Python 3.8 中只有位置参数的极端情况?

Corner case with positional-only parameters in Python 3.8?

我正在摆弄 PEP 570 and introduced with Python 3.8 中指定的仅位置参数,我只是想知道一个特定的极端情况。

假设我按如下方式定义了一个函数(无论这是好的设计还是有意义):

def func(p1, p2=None, p3=None, /): 
    print(p1, p2, p3)

所以有一个必需参数(p1),后面跟着两个可选参数(p2p3)。我可以只用 p1p1p2p1p2p3:

调用函数
func(1)       # 1, None, None
func(1, 2)    # 1, 2, None
func(1, 2, 3) # 1, 2, 3

但是我无法只用 p1p3 的参数来调用它,同时保持 p2 的默认值,因为我无法提供关键字参数:

func(1, p3=3)

这当然会引发TypeError:

TypeError: func() got some positional-only arguments passed as keyword arguments: 'p3'

我找不到关于这种情况的任何讨论或示例,因为 PEP 570 中的所有示例仅包含一个可选参数作为仅位置参数的一部分:

def name(p1, p2, /, p_or_kw, *, kw):
def name(p1, p2=None, /, p_or_kw=None, *, kw):
def name(p1, p2=None, /, *, kw):
def name(p1, p2=None, /):
def name(p1, p2, /, p_or_kw):
def name(p1, p2, /):

所以我的问题是:这是预期的行为,让调用者从左到右提供多个可选参数,以强制顺序覆盖它们吗?这实际上是仅位置参数的一个特征吗?

Is that the intended behavior, to have a caller provide multiple optional arguments from left to right, overriding them in a forced order? Is this actually a feature of positional-only arguments?

不仅是位置参数的 "intended behavior",它几乎就是位置参数的定义。

func(1, p3=3) 与函数签名中 / 的使用直接矛盾,因为它为仅接受位置参数的函数提供关键字参数。 p2 有一个默认值这一事实是无关紧要的(尽管它几乎没有用,就像你发现的那样)。

我会继续在文档中寻找明确的解释,但可能没有。这基本上是使用 /.

的直接暗示

不过,PEP570 includes this example:

def name(positional_only_parameters, /, positional_or_keyword_parameters,
         *, keyword_only_parameters):

这表明我们可以将 func 重写为:

def func(p1,p3=None, /, p2=None):
    print(p1, p2, p3)

那么这两个都有效:

func(1, 3)
func(1, 3, 2)

输出为

1 None 3
1 2 3

Try it online

eval 内置函数是描述让我感到困惑的行为的一个很好的例子,它有两个仅限位置的可选参数:

eval(source, globals=None, locals=None, /)
    Evaluate the given source in the context of globals and locals.

    The source may be a string representing a Python expression
    or a code object as returned by compile().
    The globals must be a dictionary and locals can be any mapping,
    defaulting to the current globals and locals.
    If only globals is given, locals defaults to it.

所以无法指定只有 locals:

>>> eval("1 + 2 + x", locals={'x': 3})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: eval() takes no keyword arguments

在位置上,必须先提供globals(如果省略locals,它们默认为globals):

>>> eval("1 + 2 + x", {'x': 3}) # globals = locals
6

... 或者,如果它们应该不同:

>>> eval("1 + 2 + x", {'x': 3}, {'x': 4})
7

所以,回答这个问题:这正是预期的行为。