Вызов функции Python с * args, ** kwargs и необязательными аргументами / аргументами по умолчанию

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

def func(kw1=None,kw2=None,**kwargs):
   ...

В этом случае я могу назвать func как:

func(kw1=3,kw2=4,who_knows_if_this_will_be_used=7,more_kwargs=Ellipsis)

Я также могу определить функцию как:

def func(arg1,arg2,*args):
    ...

который можно назвать

func(3,4,additional,arguments,go,here,Ellipsis)

Наконец, я могу объединить две формы

def func(arg1,arg2,*args,**kwargs):
    ...

Но, что не работает, так это звонки:

func(arg1,arg2,*args,kw1=None,kw2=None,**kwargs):  #SYNTAX ERROR (in python 2 only,  apparently this works in python 3)
    ...

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

def func(arg1,arg2,*args,kw1=None):
    ...

можно назвать

func(1,2,3) #kw1 will be assigned 3

Таким образом, это внесет некоторую двусмысленность в вопрос о том, следует ли 3 упаковывать в args или kwargs. Однако в python 3 есть возможность указывать аргументы только по ключевым словам:

def func(a,b,*,kw=None):  #can be called as func(1,2), func(1,2,kw=3), but NOT func(1,2,3)
   ...

При этом кажется, что нет синтаксической двусмысленности с:

def func(a,b,*args,*,kw1=None,**kwargs):
    ...

Однако это по-прежнему вызывает синтаксическую ошибку (проверено с Python3.2). Есть ли причина, по которой мне не хватает? И есть ли способ получить поведение, описанное выше (наличие * args с аргументами по умолчанию) - я знаю, что могу смоделировать это поведение, манипулируя словарем kwargs внутри функции.


person mgilson    schedule 26.03.2012    source источник
comment
Понятия не имею, почему у вас возникла синтаксическая ошибка для def func (arg1, arg2, * args, kw1 = None, kw2 = None, ** kwargs):   -  person okm    schedule 26.03.2012
comment
@okm Поскольку я не тестировал эту версию на python 3, только на python 2. Я просто предполагал, что финальная версия будет работать, а когда это не так, я предположил, что предыдущие версии тоже не будут работать. Спасибо!.   -  person mgilson    schedule 26.03.2012
comment
Каким образом голый 3 может попасть в kwargs? Какое ключевое слово он использовал бы? Я не вижу двусмысленности. Обратите внимание, что пустой * в списке аргументов полезен, только если нет *args. Это заполнитель, который вы используете вместо *args.   -  person Sven Marnach    schedule 26.03.2012
comment
Хороший вопрос, но зачем распространять всеобщую путаницу с вызовом параметров (эти ребята в определениях функций) аргументы (эти ребята в вызовах функций)?   -  person z33k    schedule 23.03.2018


Ответы (4)


Вы можете сделать это в Python 3.

def func(a,b,*args,kw1=None,**kwargs):

Пустой * используется только тогда, когда вы хотите указать аргументы только для ключевых слов без принятия переменного количества позиционных аргументов с *args. Вы не используете два *.

Процитируем грамматику: в Python 2 у вас есть

parameter_list ::=  (defparameter ",")*
                    (  "*" identifier [, "**" identifier]
                    | "**" identifier
                    | defparameter [","] )

в Python 3 у вас есть

parameter_list ::=  (defparameter ",")*
                    (  "*" [parameter] ("," defparameter)*
                    [, "**" parameter]
                    | "**" parameter
                    | defparameter [","] )

который включает положение о дополнительных параметрах после параметра *.

ОБНОВЛЕНИЕ:

Последняя версия документации Python 3 здесь.

person agf    schedule 26.03.2012
comment
* args должен быть после аргументов ключевого слова, не так ли? например def func (a, b, kw1 = None, * args, ** kwargs): - person Nima Mousavi; 20.03.2018
comment
@Nimi Я специально показываю функцию Python 3, которая позволяет вам ставить именованные аргументы после *args, поэтому их можно использовать только по имени, а не по позиции. То, что вы показываете, позволяет заполнить kw1 по должности или имени - для вашей версии func(1, 2, 3) заполнит a, b и kw1, где в моей версии func(1, 2, 3) заполнит a, b и args, и потребует от вас сделайте func(1, 2, kw1=3), если хотите заполнить kw1. - person agf; 21.03.2018

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

def func(arg1,arg2,*args,kw1=None,kw2=None,**kwargs): #Invalid
def func(arg1,arg2,kw1=None,kw2=None,*args,**kwargs): #Valid

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

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

В этом примере ... Когда аргумент назван при вызове функции, он может быть предоставлен не по порядку. arg1 и arg2 являются обязательными параметрами, и если они не передаются в функцию как именованные аргументы, то они должны быть назначены в порядке от предоставленных безымянных аргументов. kw1 и kw2 имеют значения по умолчанию, указанные в определении функции, поэтому они не являются обязательными, но если они не указаны в качестве именованных аргументов, они будут принимать любые доступные значения из оставшихся предоставленных безымянных аргументов. Любые оставшиеся безымянные аргументы передаются функции в массиве с именем args. Любые именованные аргументы, не имеющие соответствующего имени параметра в определении функции, передаются вызову функции в словаре с именем kwargs.

person frank    schedule 22.09.2013
comment
Это синтаксически допустимо, но не работает так, как можно было бы ожидать, потому что существует неоднозначность относительно того, отображаются ли дополнительные обязательные аргументы в именованные аргументы ключевого слова или * args. - person tonycpsu; 20.12.2015
comment
Я бы пошел еще дальше и сказал, что это не работает для создания аргументов kw1 и kw2 'keyword' с Python 2.7, поэтому я здесь! Форму лучше указать как func(arg1,arg2,arg3=None,arg4=None,*args,**kwargs): #Valid with defaults on positional args, но на самом деле это всего четыре позиционных аргумента, два из которых являются необязательными. Чтобы передать kwargs, вам нужно будет заполнить все четыре аргумента, включая arg3 и arg4. - person sage; 22.03.2016
comment
Я предлагаю stackoverflow.com/a/15302038/527489 для Python 2.7 и обращаю внимание на def_val. - person sage; 22.03.2016
comment
Как упоминалось в @sage, это означает, что вам НЕОБХОДИМО заполнить kw1 и kw2, если вы хотите определить args* - person Stevoisiak; 27.02.2018
comment
Это верно только для Python 2, а не для Python 3, где аргументы, указанные после параметра *args var-positional (или одиночного *), называются только ключевые слова. Им даже не обязательно иметь значения по умолчанию. - person Martijn Pieters; 12.09.2018

Ясно и лаконично:

В Python 3.5 или более поздней версии:

def foo(a, b=3, *args, **kwargs):
  defaultKwargs = { 'c': 10, 'd': 12 }
  kwargs = { **defaultKwargs, **kwargs }
  
  print(a, b, args, kwargs)
  
  # Do something else

foo(1) # 1 3 () {'c': 10, 'd': 12}
foo(1, d=5) # 1 3 () {'c': 10, 'd': 5}
foo(1, 2, 4, d=5) # 1 2 (4,) {'c': 10, 'd': 5}

Примечание: вы можете использовать в Python 2

kwargs = merge_two_dicts(defaultKwargs, kwargs)

В Python 3.5

kwargs = { **defaultKwargs, **kwargs }

В Python 3.9

kwargs = defaultKwargs | kwargs  # NOTE: 3.9+ ONLY
person Roman    schedule 25.11.2020

Если вы хотите сделать это в Python 2, я нашел обходной путь, описанный в этом посте, с использованием декоратора.

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

from functools import wraps

def force_kwargs(**defaultKwargs):
    def decorator(f):
        @wraps(f)
        def g(*args, **kwargs):
            new_args = {}
            new_kwargs = defaultKwargs
            varnames = f.__code__.co_varnames
            new_kwargs.update(kwargs)
            for k, v in defaultKwargs.items():
                if k in varnames:
                    i = varnames.index(k)
                    new_args[(i, k)] = new_kwargs.pop(k)
            # Insert new_args into the correct position of the args.
            full_args = list(args)
            for i, k in sorted(new_args.keys()):
                if i <= len(full_args):
                    full_args.insert(i, new_args.pop((i, k)))
                else:
                    break
            # re-insert the value as a key-value pair
            for (i, k), val in new_args.items():
                new_kwargs[k] = val
            return f(*tuple(full_args), **new_kwargs)
        return g
    return decorator

Результат

@force_kwargs(c=7, z=10)
def f(a, b='B', c='C', d='D', *args, **kw):
    return a, b, c, d, args, kw
#                                    a    b  c    d  args      kwargs
f('r')                           # 'r', 'B', 7, 'D',    (),       {'z': 10}
f(1, 2, 3, 4, 5)                 #   1,   2, 7,   3, (4,5),       {'z': 10}
f(1, 2, 3, b=0, c=9, f='F', z=5) #   1,   0, 9,   2,  (3,), {'f': 'F', 'z': 5}

Вариант

Если вы хотите использовать значения по умолчанию, как написано в определении функции, вы можете получить доступ к значениям аргументов по умолчанию с помощью f.func_defaults, в котором перечислены значения по умолчанию. Вам нужно будет zip добавить к ним конец f.__code__.varnames, чтобы эти значения по умолчанию совпадали с именами переменных.

person AlexLoss    schedule 19.08.2020