【问题标题】:with `*args`, keyword arguments become positional?使用 `*args`,关键字参数变为位置?
【发布时间】:2018-02-19 00:23:07
【问题描述】:

例如,假设我定义了一个函数

def myfunc(a=1, *args):
    print(a)
    # *args have some other functionality, for example passed to a class instance, or whatever

问题是:在调用myfunc 时,我无法先传递*args 的预期位置参数,然后传递关键字参数a。例如,如果预期的*args 只包含一个数字,我不能这样做

myfunc(3, a=4)

这会引发一个错误,说明收到了多个任务。我显然不能myfunc(a=4, b=5, 3)。我只能这样做

myfunc(4, 3)

所以现在看来​​,使用*args,关键字参数a 真的变成了位置?

这里的例子是一个函数,但它也适用于类。假设一个带有__init__(a=3, *args) 的子类想要一些关键字参数a 和一些其他99 个必需参数传递给super。我不想在子类中重复 99 个参数的定义,而是做 *args 并将其传递给 super。但是现在关键字参数a 变成了位置。

这个类和子类的例子是我编码中的真正问题。我想要一个带有可选关键字参数a 的子类,然后是它的父类的99 个必需参数。我没有看到解决这个问题的好方法。

我不是第一个遇到这个问题的人,但我找不到任何答案。

【问题讨论】:

  • 我认为这样做的方法是摆脱您的a 并改用*kwargs。然后,如果您仍然想要a 的默认值,则必须在您的函数/类中实现它。准确地说,告诉 python *args 有点像是在说“任何其他会出现的论点”。因此,在您引发错误的示例中,Python 将第一个参数映射到它知道的命名参数 (a),因此当您想显式设置 a 的值时,Python 会因为它已经有一个值而感到困惑为它。
  • 感谢您的评论。你是说**kwargs?是的,我理解发生这种情况的原因,我很惊讶*args 关键字参数变成了位置。我在实际编码中真正遇到的更像是类和子类示例,我看不出有什么好的方法。
  • @Alex。我可能在这里遗漏了一些东西 - 但为什么不使用关键字参数在 *args 之后定义函数?
  • 我使用的是 Python 2.7?也许在 Python 3 中可以做到这一点?
  • @Alex。切换到 Python 3 的另一个很好的理由:P

标签: python python-2.7 function arguments keyword


【解决方案1】:

Python 3 引入了仅限关键字的参数:https://www.python.org/dev/peps/pep-3102/

所以你可以这样做:

def myfunc(*args, a=1):
    print(a)

然后myfunc(3, a=4) 就可以正常工作了。

【讨论】:

    【解决方案2】:

    a 不仅仅是位置:

    >>> import inspect
    >>> def myfunc(a=1, *args):
    ...     print(a)
    >>> inspect.signature(myfunc).parameters['a'].kind
    <_ParameterKind.POSITIONAL_OR_KEYWORD: 1>
    

    确实可以作为关键字参数传入:

    >>> myfunc(a=4)
    4
    

    但是,正如您所指出的,只要您想提供后续位置参数,就必须将其作为位置参数提供。但这与*args 无关,这就是位置参数的工作原理:

    >>> def myfunc2(a, b):
    ...     print(a)
    >>> myfunc2(1, a=2)
    TypeError: myfunc2() got multiple values for argument 'a'
    

    它必须是所有位置参数中的第一个或所有参数都必须按名称传递。

    但是*args 使情况有些复杂,因为它只收集位置参数。这意味着如果它应该收集 任何东西,您还必须按位置传递所有先前的参数。

    但如果您想推出自己的特殊情况,您可以只接受 *args, **kwargs 并首先检查它是否作为关键字(命名)参数传递,或者如果不是,作为第一个位置参数传递:

    >>> def myfunc3(*args, **kwargs):
    ...     if 'a' in kwargs:
    ...         a = kwargs.pop('a')
    ...     else:
    ...         a = args[0]
    ...         args = args[1:]
    ...     print(a, args, kwargs)
    >>> myfunc3(1, a=2)
    2 (1,) {}
    >>> myfunc3(2, 1)
    2 (1,) {}
    

    这应该会给您预期的结果,但您需要在文档中明确说明应如何传入参数,因为它在某种程度上破坏了 Python 签名模型的语义。

    【讨论】:

    • 感谢您的回答。该解决方案确实有效,但是,正如您所指出的,它对代码来说有点嘈杂。在这一点上我很惊讶。我想我想要的在 python 编程中很常见:你有一个基类,然后创建一个接受一些可选关键字参数的子类,并绕过其他 args 到 super。但显然,至少在 Python 2.7 中没有干净的方法。
    • @Alex 是的,这是一个常见的用例,但*args 和/或**kwargs 的存在总是会破坏签名(例如inspect.getargspec 或inspect.signature)。因此,在 Python 中,通常只是“复制参数”并添加您想要添加的参数或删除您不需要的参数。因为这保留了内省,并且不需要像我在答案中提到的那样“黑客”。
    猜你喜欢
    • 2023-03-31
    • 2023-04-06
    • 1970-01-01
    • 2018-12-12
    • 2021-02-26
    • 2015-09-20
    • 2020-11-03
    • 2020-11-18
    • 1970-01-01
    相关资源
    最近更新 更多