使用*args和**kwargs的默认参数

105
在Python 2.x (我使用2.7),使用*args**kwargs的默认参数的正确方法是什么? 我在SO上找到了一个相关主题的问题,但那是针对Python 3的: 使用*args,**kwargs和可选/默认参数调用Python函数 在那里,他们说这种方法可行:
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']
    #...

迄今为止我遇到的最简洁的解释:http://www.saltycrane.com/blog/2008/01/how-to-use-args-and-kwargs-in-python/ - verbsintransit
6
永远不要明确地调用__contains__。始终使用in: 'opt_arg' in kwargs。(更好的方式是像mgilson的答案中那样使用kwargs.get('opt_arg', 'def_val'))。 - nneonneo
7个回答

117

将默认参数放在*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 中也可以工作。在 Python 3 中,将默认参数 放在 *args 后面会使它成为一个“仅限关键字”的参数,只能通过名称指定,而不能通过位置指定。如果您想在 Python 2 中使用仅限关键字参数,则可以使用 @mgilson 的解决方案


1
是的,这是由于*args的歧义性。如果您想要一个仅限关键字的参数,那么您的方法是正确的。 - nneonneo
刚才在上面的评论中已经回答了这个问题。 (如果我想用不同的b调用函数,而且我还想添加*args怎么办?) - user1563285
无论如何,在询问之前,我尝试了这个解决方案,但我发现只有在定义opt_arg后仅使用kwargs时才有效。 - user1563285
@nneonneo:我收到了你的示例,但是这种方式仍然不允许我们同时指定默认参数和*args,就像Python 3.x所允许的那样,在链接中解释过;是吗? - user1563285
7
在填写 *args 的同时,不能将默认参数保持不变。这就是为什么 Python 3 添加了这个功能的原因。通常,在 Python 2 中,可以将默认参数指定为明显的值,如 0 或 None,以便可以显式地传递它们。 - nneonneo
仅供参考,这个解决方案在 pylint 中会发出警告,说:“keyword-arg-before-vararg”。为了避免这个警告,你可以使用 def foo(a, *args, b=3, **kwargs): 的形式。但是这意味着你必须始终将 b 的值作为关键字参数传递。 - amin_nejad

63

另一个问题中的语法仅适用于Python 3.x,并指定关键字参数。它在Python 2.x上不起作用。

对于Python 2.x,我会从kwargs中使用pop弹出它:

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

它还在默认参数之前指定了*args。 - user1563285

22

类似于 @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

5
您可以使用如下装饰器来实现这个功能:
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'})

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)

与 @nneonneo 的答案不同,这处理将任意 *args 传递给底层调用。


1
如果您使用有效的代码,例如'opt_arg'代替<kwarg-name>以及'def_val'代替<kwarg-name-default-value>,那将更清晰易懂。 - wjandrea

0

这个答案是Daniel Américo建议的扩展。

如果没有严格定义,这个装饰器会分配默认的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 访问参数的默认值,它会列出默认值。你需要将它们与 f.__code__.varnames 的结尾进行 zip 匹配,以使这些默认值与变量名匹配。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接