Аргументы по умолчанию с * args и ** kwargs

86

В Python 2.x (я использую 2.7), как правильно использовать аргументы по умолчанию с *argsи **kwargs?
Я нашел вопрос по SO, связанный с этой темой, но он касается Python 3 :
вызов функции Python с * args, ** kwargs и необязательными / аргументами по умолчанию

Там говорят, что этот метод работает:

def func(arg1, arg2, *args, opt_arg='def_val', **kwargs):
    #...

В 2.7 это приводит к файлу SyntaxError. Есть ли рекомендуемый способ определения такой функции?
У меня это работает таким образом, но я думаю, что есть более хорошее решение.

def func(arg1, arg2, *args, **kwargs):
    opt_arg ='def_val'
    if kwargs.__contains__('opt_arg'):
        opt_arg = kwargs['opt_arg']
    #...
Codeforester
источник
Тем не менее, наиболее емкое объяснение, которое я встречал: saltycrane.com/blog/2008/01/…
verbsintransit
4
Никогда не звоните явно __contains__. Всегда используйте in: 'opt_arg' in kwargs. (Еще лучше: kwargs.get('opt_arg', 'def_val')как в ответе Мильсона).
nneonneo 08

Ответы:

81

Просто поместите аргументы по умолчанию перед *args:

def foo(a, b=3, *args, **kwargs):

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

Примеры:

foo(x) # a=x, b=3, args=(), kwargs={}
foo(x, y) # a=x, b=y, args=(), kwargs={}
foo(x, b=y) # a=x, b=y, args=(), kwargs={}
foo(x, y, z, k) # a=x, b=y, args=(z, k), kwargs={}
foo(x, c=y, d=k) # a=x, b=3, args=(), kwargs={'c': y, 'd': k}
foo(x, c=y, b=z, d=k) # a=x, b=z, args=(), kwargs={'c': y, 'd': k}

Обратите внимание, что, в частности, foo(x, y, b=z)не работает, потому bчто в этом случае назначается по должности.


Этот код работает и в Python 3. Установка аргумента по умолчанию после*args в Python 3 делает его аргументом «только для ключевых слов», который можно указывать только по имени, а не по позиции. Если вам нужен аргумент только с ключевыми словами в Python 2, вы можете использовать решение @mgilson.

nneonneo
источник
1
Да, это из-за неоднозначности *args. Ваш способ верен, если вам нужен аргумент, состоящий только из ключевых слов.
nneonneo 08
Просто получил ответ в комментариях выше. (Что, если я хочу вызвать функцию с другим b, а также хочу добавить * args?)
В любом случае, прежде чем спросить, я попробовал это решение, но также обнаружил, что оно работает, только если после определения opt_arg я использую только kwargs.
@nneonneo: Я получил ваши примеры, но этот способ по-прежнему не дает нам свободы указывать аргументы по умолчанию и * args одновременно, как это позволяет Python 3.x, как объяснено в ссылке; Имеет ли это?
5
Вы не можете оставить аргумент по умолчанию, одновременно заполняя его *args, нет. Вот почему эта функциональность была добавлена ​​в Python 3. Обычно аргументы по умолчанию в Python 2 указываются как нечто очевидное, например 0 или Noneоколо того, чтобы их можно было явно передать.
nneonneo
55

Синтаксис в другом вопросе - только python3.x и указывает аргументы только по ключевым словам. Это не работает на python2.x.

Для python2.x я бы popиз kwargs:

def func(arg1, arg2, *args, **kwargs):
    opt_arg = kwargs.pop('opt_arg', 'def_val')
мгильсон
источник
Он также указывает * args перед аргументом по умолчанию.
4

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

import functools
def default_kwargs(**defaultKwargs):
    def actual_decorator(fn):
        @functools.wraps(fn)
        def g(*args, **kwargs):
            defaultKwargs.update(kwargs)
            return fn(*args, **defaultKwargs)
        return g
    return actual_decorator

Тогда просто сделайте:

@default_kwargs(defaultVar1 = defaultValue 1, ...)
def foo(*args, **kwargs):
    # Anything in here

Например:

@default_kwargs(a=1)
def f(*args, **kwargs):
    print(kwargs['a']+ 1)

f() # Returns 2
f(3) # Returns 4
Даниэль Америко
источник
1

Придерживаясь вашего подхода к решению, пытаясь сделать его более общим и компактным, я бы предложил рассмотреть что-то вроде этого:

>>> def func(arg1, arg2, *args, **kwargs):
...     kwargs_with_defaults = dict({'opt_arg': 'def_val', 'opt_arg2': 'default2'}, **kwargs)
...     #...
...     return arg1, arg2, args, kwargs_with_defaults

>>> func('a1', 'a2', 'a3', 'a5', x='foo', y='bar')
('a1', 'a2', ('a3', 'a5'), {'opt_arg2': 'default2', 'opt_arg': 'def_val', 'y': 'bar', 'x': 'foo'})

>>> func('a1', 'a2', 'a3', 'a5', opt_arg='explicit_value', x='foo', y='bar')
('a1', 'a2', ('a3', 'a5'), {'opt_arg2': 'default2', 'opt_arg': 'explicit_value', 'y': 'bar', 'x': 'foo'})
яккоб
источник
1

Подобный подход к @yaccob, но ясный и лаконичный:

В 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    

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
Роман
источник
0

Другой способ работы с Python 2.x:

def foo(*args, **kwargs):
    if 'kwarg-name' not in kwargs.keys():
        kwargs['kwarg-name'] = 'kwarg-name-default-value'
    return bar(*args, **kwargs)

Это обрабатывает произвольную передачу *argsбазовому вызову в отличие от ответа @ nneonneo.

conner.xyz
источник
1
Это было бы яснее, если бы вы использовали действительный код, то есть 'opt_arg'вместо < kwarg-name >и 'def_val'вместо< kwarg-name-default-value >
wjandrea
0

Этот ответ является продолжением того, что предложил Даниэль Америко .

Этот декоратор назначает значения 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)
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',   (),         {}
f(1,2,3)                    #   1,   2, 7,   3,   (),         {}
f(1, 2, 3, b=3, c=9, f='F') #   1,   3, 9,   2, (3,), {'f': 'F'}

Вариант

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

AlexLoss
источник