Принудительное именование параметров в Python - PullRequest
78 голосов
/ 03 июня 2010

В Python у вас может быть определение функции:

def info(object, spacing=10, collapse=1)

, который можно вызвать любым из следующих способов:

info(odbchelper)                    
info(odbchelper, 12)                
info(odbchelper, collapse=0)        
info(spacing=15, object=odbchelper)

благодаря тому, что Python допускает аргументы любого порядка, если они названы.

Проблема, с которой мы сталкиваемся, заключается в том, что по мере роста некоторых из наших больших функций люди могут добавлять параметры в диапазоне от spacing до collapse, что означает, что неправильные значения могут идти к параметрам, которые не названы. Кроме того, иногда не всегда понятно, что нужно делать. Мы ищем способ заставить людей называть определенные параметры - не просто стандарт кодирования, а в идеале плагин flag или pydev?

так, чтобы в приведенных выше 4 примерах только последний прошел проверку, так как все параметры названы.

Скорее всего, мы включим его только для определенных функций, но любые предложения относительно того, как это реализовать - или, если это вообще возможно, будут оценены.

Ответы [ 11 ]

158 голосов
/ 13 января 2013

В Python 3 - Да, вы можете указать * в списке аргументов.

Из документов :

Параметры после «*» или «* идентификатор» являются параметрами только для ключевых слов и могут передаваться только используемые ключевые аргументы.

>>> def foo(pos, *, forcenamed):
...   print(pos, forcenamed)
... 
>>> foo(pos=10, forcenamed=20)
10 20
>>> foo(10, forcenamed=20)
10 20
>>> foo(10, 20)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foo() takes exactly 1 positional argument (2 given)

Это также можно комбинировать с **kwargs:

def foo(pos, *, forcenamed, **kwargs):
24 голосов
/ 22 августа 2012

Вы можете заставить людей использовать ключевые аргументы в Python3, определив функцию следующим образом.

def foo(*, arg0="default0", arg1="default1", arg2="default2"):
    pass

Делая первый аргумент позиционным аргументом без имени, вы заставляете всех, кто вызывает функцию, использовать ключевое слово arguments, о чем, я думаю, вы спрашивали. В Python2 единственный способ сделать это - определить функцию, подобную этой

def foo(**kwargs):
    pass

Это заставит вызывающего абонента использовать kwargs, но это не очень хорошее решение, так как вам придется поставить чек, чтобы принять только тот аргумент, который вам нужен.

10 голосов
/ 03 июля 2013

True, большинство языков программирования делают порядок параметров частью контракта вызова функции, но для этого не требуется , чтобы быть таковым. С чего бы это? Таким образом, я понимаю вопрос, если Python в этом отношении отличается от других языков программирования. В дополнение к другим хорошим ответам для Python 2, пожалуйста, учтите следующее:

__named_only_start = object()

def info(param1,param2,param3,_p=__named_only_start,spacing=10,collapse=1):
    if _p is not __named_only_start:
        raise TypeError("info() takes at most 3 positional arguments")
    return str(param1+param2+param3) +"-"+ str(spacing) +"-"+ str(collapse)

Единственный способ, которым вызывающая сторона сможет предоставить аргументы spacing и collapse позиционно (без исключения), будет:

info(arg1, arg2, arg3, module.__named_only_start, 11, 2)

Соглашение об использовании закрытых элементов, принадлежащих другим модулям, уже является очень базовым в Python. Как и в самом Python, это соглашение о параметрах будет применяться только наполовину.

В противном случае вызовы должны быть в форме:

info(arg1, arg2, arg3, spacing=11, collapse=2)

Звонок

info(arg1, arg2, arg3, 11, 2)

присвоит значение 11 параметру _p и исключение, вызванное первой инструкцией функции.

Характеристики:

  • Параметры до _p=__named_only_start принимаются позиционно (или по имени).
  • Параметры после _p=__named_only_start должны предоставляться только по имени (кроме случаев, когда получены и используются знания о специальном часовом объекте __named_only_start).

Плюсы:

  • Параметры являются явными по количеству и значению (конечно, позже, если хорошие имена также выбраны).
  • Если в качестве первого параметра указан часовой, то все аргументы должны быть указаны по имени.
  • При вызове функции можно переключиться в позиционный режим, используя часовой объект __named_only_start в соответствующей позиции.
  • Можно ожидать лучшую производительность, чем другие альтернативы.

Минусы:

  • Проверка происходит во время выполнения, а не во время компиляции.
  • Использование дополнительного параметра (хотя и не аргумента) и дополнительной проверки. Небольшое снижение производительности по сравнению с обычными функциями.
  • Функциональность - это хак без прямой поддержки языка (см. Примечание ниже).
  • При вызове функции можно переключиться в позиционный режим, используя часовой объект __named_only_start в правильном положении. Да, это также можно рассматривать как профессионал.

Пожалуйста, имейте в виду, что этот ответ действителен только для Python 2. Python 3 реализует похожий, но очень элегантный механизм с поддержкой языка, описанный в других ответах.

Я обнаружил, что когда я открываю свой разум и думаю об этом, никакие вопросы или решения других не кажутся глупыми, глупыми или просто глупыми. Совсем наоборот: я обычно многому учусь.

7 голосов
/ 31 мая 2017

Вы можете сделать это способом , который работает как в Python 2, так и в Python 3 , создав «поддельный» аргумент первого ключевого слова со значением по умолчанию, которое не будет происходить «естественно». Этому ключевому аргументу может предшествовать один или несколько аргументов без значения:

_dummy = object()

def info(object, _kw=_dummy, spacing=10, collapse=1):
    if _kw is not _dummy:
        raise TypeError("info() takes 1 positional argument but at least 2 were given")

Это позволит:

info(odbchelper)        
info(odbchelper, collapse=0)        
info(spacing=15, object=odbchelper)

но не:

info(odbchelper, 12)                

Если вы измените функцию на:

def info(_kw=_dummy, spacing=10, collapse=1):

тогда все аргументы должны иметь ключевые слова и info(odbchelper) больше не будет работать.

Это позволит вам размещать дополнительные аргументы ключевых слов в любом месте после _kw, не заставляя вас ставить их после последней записи. Это часто имеет смысл, например, Группировка вещей по логике или упорядочение ключевых слов по алфавиту могут помочь в обслуживании и развитии.

Таким образом, нет необходимости возвращаться к использованию def(**kwargs) и потере информации о подписи в вашем интеллектуальном редакторе. Ваш социальный контракт заключается в предоставлении определенной информации, вынуждая (некоторые из них) запрашивать ключевые слова, порядок их представления стал неактуальным.

2 голосов
/ 08 мая 2019

Аргументы только для ключевых слов python3 (*) можно смоделировать в python2.x с помощью **kwargs

Рассмотрим следующий код python3:

def f(pos_arg, *, no_default, has_default='default'):
    print(pos_arg, no_default, has_default)

и его поведение:

>>> f(1, 2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() takes 1 positional argument but 3 were given
>>> f(1, no_default='hi')
1 hi default
>>> f(1, no_default='hi', has_default='hello')
1 hi hello
>>> f(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() missing 1 required keyword-only argument: 'no_default'
>>> f(1, no_default=1, wat='wat')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() got an unexpected keyword argument 'wat'

Это может быть смоделировано с использованием следующего, заметьте, я позволил себе переключить TypeError на KeyError в случае «требуемого именованного аргумента», было бы не слишком много работы, чтобы сделать это то же исключение типа также

def f(pos_arg, **kwargs):
    no_default = kwargs.pop('no_default')
    has_default = kwargs.pop('has_default', 'default')
    if kwargs:
        raise TypeError('unexpected keyword argument(s) {}'.format(', '.join(sorted(kwargs))))

    print(pos_arg, no_default, has_default)

И поведение:

>>> f(1, 2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() takes exactly 1 argument (3 given)
>>> f(1, no_default='hi')
(1, 'hi', 'default')
>>> f(1, no_default='hi', has_default='hello')
(1, 'hi', 'hello')
>>> f(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in f
KeyError: 'no_default'
>>> f(1, no_default=1, wat='wat')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in f
TypeError: unexpected keyword argument(s) wat

Рецепт работает одинаково хорошо в python3.x, но его следует избегать, если вы используете только python3.x

2 голосов
/ 03 июня 2010

Обновление:

Я понял, что использование **kwargs не решит проблему.Если ваши программисты изменяют аргументы функции по своему усмотрению, можно, например, изменить функцию на следующее:

def info(foo, **kwargs):

, и старый код снова сломается (потому что теперь каждый вызов функции должен включать первый аргумент).

Это действительно сводится к тому, что говорит Брайан.


(...) люди могут добавлять параметры между spacing и collapse (...)

В общем случае при изменении функций новые аргументы всегда должны заканчиваться.В противном случае это нарушает код.Должно быть очевидно.
Если кто-то изменяет функцию так, что код нарушается, это изменение должно быть отклонено.
(Как говорит Брайан, это похоже на контракт)

(...) иногда не всегда понятно, что нужно делать.

Просматривая сигнатуру функции (т. е. def info(object, spacing=10, collapse=1)), сразу следует увидеть, что каждый аргумент, имеющий не значение по умолчанию, является обязательным.
Для чего аргумент, для которого необходимо указать строку документации.


Старый ответ (сохранено для полноты) :

Это, вероятно, не очень хорошее решение:

Вы можете определить функции следующим образом:

def info(**kwargs):
    ''' Some docstring here describing possible and mandatory arguments. '''
    spacing = kwargs.get('spacing', 15)
    obj = kwargs.get('object', None)
    if not obj:
       raise ValueError('object is needed')

kwargs - словарь, содержащий любой аргумент ключевого слова.Вы можете проверить, присутствует ли обязательный аргумент, и если нет, вызвать исключение.

Недостатком является то, что может быть уже не так очевидно, какие аргументы возможны, но с правильной строкой документации это должно бытьштраф.

1 голос
/ 03 июня 2010

Вы можете объявить свои функции только как получающие **args. Это потребовало бы аргументов ключевого слова, но у вас была бы дополнительная работа, чтобы убедиться, что передаются только допустимые имена.

def foo(**args):
   print args

foo(1,2) # Raises TypeError: foo() takes exactly 0 arguments (2 given)
foo(hello = 1, goodbye = 2) # Works fine.
0 голосов
/ 03 июня 2010

Я не понимаю, почему программист в первую очередь добавляет параметр между двумя другими.

Если вы хотите, чтобы параметры функции использовались с именами (например, info(spacing=15, object=odbchelper)), тогда не должно иметь значения, в каком порядке они определены, поэтому вы также можете поместить новые параметры в конец.

Если вы хотите, чтобы заказ имел значение, тогда не ожидайте, что что-то сработает, если вы его измените!

0 голосов
/ 03 июня 2010

Как говорят другие ответы, изменение сигнатур функций - плохая идея.Либо добавьте новые параметры в конец, либо исправьте каждого вызывающего, если аргументы вставлены.

Если вы все еще хотите это сделать, используйте функцию декоратора и inspect.getargspec функция.Было бы использовано что-то вроде этого:

@require_named_args
def info(object, spacing=10, collapse=1):
    ....

Реализация require_named_args оставлена ​​в качестве упражнения для читателя.

Я бы не стал беспокоиться.Это будет медленным при каждом вызове функции, и вы получите лучшие результаты от более тщательного написания кода.

0 голосов
/ 03 июня 2010
def cheeseshop(kind, *arguments, **keywords):

в python, если используется * args, это означает, что вы можете передать n-количество аргументов для этого параметра - который будет входить в список внутри функции для доступа к

и если использовать ** kw, что означает аргументы его ключевого слова, доступ к которому можно получить как dict - вы можете передать n-количество аргументов kw, и если вы хотите ограничить этого пользователя, необходимо ввести последовательность и аргументы по порядку, затем don не использовать * и ** - (его питонский способ предоставления универсальных решений для больших архитектур ...)

если вы хотите ограничить вашу функцию значениями по умолчанию, вы можете проверить внутри нее

def info(object, spacing, collapse)
  spacing = spacing or 10
  collapse = collapse or 1
...