70

在 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 打包到argsorkwargs中。但是,使用 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

4

4 回答 4

71

可以在 Python 3 中做到这一点。

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

*当您想指定仅关键字参数而不接受可变数量的位置参数时,才使用*args. 你不使用两个*s。

引用语法,在Python 2中,你有

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

而在Python 3中,你有

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

其中包括在参数之后的附加参数的规定*

更新:

最新的 Python 3 文档在这里

于 2012-03-26T13:44:14.980 回答
8

如果您想混合使用两者,请记住 *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 的字典中提供给函数调用。

于 2013-09-22T00:12:42.937 回答
4

简洁明了:

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
于 2020-11-25T11:16:08.363 回答
0

如果您希望在 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加上变量名来匹配这些默认值。

于 2020-08-19T13:19:12.417 回答