I am fiddling around with positional-only parameters as specified in PEP 570 and introduced with Python 3.8, and I was just wondering about a specific corner case.
Let's say I define a function as follows (no matter whether that is good design or makes any sense at all):
def func(p1, p2=None, p3=None, /):
print(p1, p2, p3)
So there is one required parameter (p1
), followed by two optional parameters (p2
and p3
). I can call the function with just p1
, p1
and p2
or p1
and p2
and p3
:
func(1) # 1, None, None
func(1, 2) # 1, 2, None
func(1, 2, 3) # 1, 2, 3
But there is no way I can ever just call it with p1
and an argument for p3
while keeping the default for p2
, as I can not provide keyword arguments:
func(1, p3=3)
This will of course raise a TypeError
:
TypeError: func() got some positional-only arguments passed as keyword arguments: 'p3'
I couldn't find any discussion or examples on this case, as all of the examples in PEP 570 just cover a single optional parameter as part of the positional-only arguments:
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, /):
So my question is: 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?
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?
Not only that is the "intended behavior" of positional-arguments, it's pretty much the definition of it.
func(1, p3=3)
directly contradicts the use of /
in the function's signature as it provides a keyword argument to a function that accepts only positional arguments. The fact that p2
has a default value is irrelevant (though it's pretty much useless as you found).
I will keep looking for an explicit explanation in the documentation but there might not be one. It's basically a straightforward implication of using /
.
However, PEP570 includes this example:
def name(positional_only_parameters, /, positional_or_keyword_parameters,
*, keyword_only_parameters):
Which suggests we can rewrite func
as:
def func(p1,p3=None, /, p2=None):
print(p1, p2, p3)
Then both of these work:
func(1, 3)
func(1, 3, 2)
Output is
1 None 3
1 2 3