Принудительное именование параметров в Python

В 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, что означает, что неправильные значения могут иметь параметры, которые не называются. Кроме того, иногда не всегда понятно, что нужно делать. Мы заставим людей называть определенные параметры, а не только стандарт кодирования, но в идеале это плагин или плагин pydev?

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

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

Ответ 1

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

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

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

>>> 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):

Ответ 2

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

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

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

def foo(**kwargs):
    pass

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

Ответ 3

Правда, большинство языков программирования составляют часть заказа параметра в контракте вызова функции, но это не обязательно так. Почему? Мое понимание вопроса заключается в том, что если 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 реализует аналогичный, но очень элегантный, поддерживаемый языком механизм, описанный в других ответах.

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

Ответ 4

Вы можете сделать это способом , который работает как на 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) и потерять информацию подписи в вашем интеллектуальном редакторе. Ваш социальный контракт заключается в предоставлении определенной информации, заставляя (некоторые из них) требовать ключевые слова, порядок, в котором они представлены, стал неактуальным.

Ответ 5

Update:

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

def info(foo, **kwargs):

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

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


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

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

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

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


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

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

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

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 - словарь, содержащий любой аргумент ключевого слова. Вы можете проверить, присутствует ли обязательный аргумент, а если нет, поднимите исключение.

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

Забастовкa >

Ответ 6

Вы можете объявлять свои функции только при приеме **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.

Ответ 7

Вы можете использовать оператор **:

def info(**kwargs):

Таким образом, люди вынуждены использовать именованные параметры.

Ответ 8

def cheeseshop(kind, *arguments, **keywords):

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

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

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

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

Ответ 9

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

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

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

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

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

Ответ 10

Аргументы только для ключевых слов 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

Ответ 11

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

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

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