使用`*args`,关键字参数变成位置?

with `*args`, keyword arguments become positional?

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

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)

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

myfunc(4, 3)

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

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

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

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

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 使情况变得有些复杂,因为它只收集位置参数。这意味着如果它应该收集 anything 你还必须按位置传递所有先前的参数。

但是如果你想推出自己的特殊情况,你可以只接受 *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 3 引入了仅关键字参数:https://www.python.org/dev/peps/pep-3102/

所以你可以这样做:

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

然后 myfunc(3, a=4) 就可以了。