【问题标题】:Calling a Python function with *args,**kwargs and optional / default arguments使用 *args、**kwargs 和可选/默认参数调用 Python 函数
【发布时间】:2012-04-09 23:56:50
【问题描述】:

在 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 是否应该打包到 argskwargs 方面引入一些歧义。但是,在 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 字典来模拟这种行为。

【问题讨论】:

  • 不知道为什么“def func(arg1,arg2,*args,kw1=None,kw2=None,**kwargs):”出现语法错误
  • @okm 因为我没有在 python 3 上测试那个版本,只在 python 2 上测试过。我只是假设最终版本可以工作,如果没有,我假设以前的版本不会工作。谢谢!。
  • 一个裸露的3 应该如何进入kwargs?它会使用什么关键字?我看不出有任何歧义。请注意,参数列表中的 * 仅在没有 *args 时才有用。这是一个占位符,您使用 而不是 *args
  • 好问题,但为什么要散布调用 parameters(函数定义中的那些人)arguments世界范围的混乱 (函数调用中的那些家伙)?

标签: python python-3.x keyword-argument


【解决方案1】:

简洁明了:

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

【讨论】:

    【解决方案2】:

    如果您希望在 Python 2 中执行此操作,我在 this post 中找到了使用装饰器解释的解决方法。

    如果没有严格定义,这个装饰器会分配默认的 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 访问参数默认值。您必须在 f.__code__.varnames 末尾加上 zip 以将这些默认值与变量名匹配。

    【讨论】:

      【解决方案3】:

      可以在 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 文档here

      【讨论】:

      • *args 应该在关键字参数之后,不是吗?例如def func(a,b, kw1=None, *args, **kwargs):
      • @Nimi 我专门展示了一个仅限 Python 3 的功能,它允许您在*args 之后放置命名参数,因此它们只能按名称使用,而不是位置。你所显示的内容允许kw1按位置或名称填写——对于你的版本func(1, 2, 3)将填写abkw1,在我的版本中func(1, 2, 3)将填写在abargs中,如果你想填写kw1,需要你做func(1, 2, kw1=3)
      【解决方案4】:

      如果您想混合使用两者,请记住 *args 和 **kwargs 必须是最后指定的参数。

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

      cmets 似乎是基于将函数定义的构造方式与提供的参数分配回定义中指定的参数的方式相比较。

      这是这个函数的定义,它有6个参数。 通过在函数调用中将命名和未命名参数传递给它来调用它。

      对于这个例子... 当调用函数时命名参数时,可以无序提供。 arg1 和 arg2 是强制参数,如果没有作为命名参数传递给函数,那么它们必须按照提供的未命名参数的顺序分配。 kw1 和 kw2 在函数定义中提供了默认值,因此它们不是强制性的,但如果没有作为命名参数提供,它们将从其余提供的未命名参数中获取任何可用值。 剩下的任何未命名参数都将在名为 args 的数组中提供给函数 任何在函数定义中没有对应参数名称的命名参数都会在名为 kwargs 的字典中提供给函数调用。

      【讨论】:

      • 这在语法上是有效的,但并不像人们预期的那样工作,因为对于额外的必需参数是否映射到命名的关键字参数或 *args 存在歧义。
      • 我会更进一步说,这不适用于使用 Python 2.7 制作 kw1 和 kw2 的“关键字”参数,这就是我在这里的原因!该表单最好列为func(arg1,arg2,arg3=None,arg4=None,*args,**kwargs): #Valid with defaults on positional args,但这实际上只是四个位置参数,其中两个是可选的。要通过 kwargs,您需要填写 所有四个 args,包括 arg3 和 arg4。
      • 我建议 Python 2.7 使用 stackoverflow.com/a/15302038/527489 并注意“def_val”。
      • 正如@sage 所说,这意味着如果要定义args*,则需要填写kw1kw2
      • 这仅适用于 Python 2,不适用于 Python 3,其中在 *args var-positional 参数(或单独的 *)之后指定的参数称为 keyword-only parameters。这些甚至不需要有默认值。
      猜你喜欢
      • 1970-01-01
      • 2015-01-28
      • 2018-07-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-06
      • 1970-01-01
      • 2016-08-22
      相关资源
      最近更新 更多