Currently, Python doesn't allow non-default arguments after default
arguments:
>>> def foo(x=None, y): pass
File "<stdin>", line 1
def foo(x=None, y): pass
^
SyntaxError: non-default argument follows default argument
I believe that at the time this was introduced, no use cases for this
were known and this is is supposed to prevent a source of bugs. I have
two use cases for this, one fringe, but valid, the other more important:
The fringe use case: Suppose you have a function that takes a 2D
coordinate value as separate "x" and "y" arguments. The "x" argument is
optional, the "y" argument isn't. Currently there are two ways to do
this, none of them particularly great:
def foo(y, x): # reverse x and y arguments, confusing
...
def foo(x, y=None): # Treat the x argument as y if only one argument is
provided
if y is None:
x, y = y, x
...
To me, the "natural" solution looks like this:
def foo(x=None, y): ...
# Called like this:
foo(1, 2)
foo(y=2)
This could also be useful when evolving APIs. For example there is a
function "bar" that takes two required arguments. In a later version,
the first argument gains a useful default, the second doesn't. There is
no sensible way to evolve the API at the moment.
The more important use case involves @overloads. A condensed example of
a function where the return type depends on an "encoding" parameter,
followed by further parameters that could be called like this:
foo(123, "utf-8") # would return bytes
foo(encoding="utf-8")
foo(123, None) # would return str
foo(encoding=None)
foo(x=123) # would return str
foo()
This could ideally be written as:
@overload
def foo(x: int = ..., encoding: None = ...) -> str: ...
@overload
def foo(x: int = ..., encoding: str) -> bytes: ...
# plus the actual implementation
But due to the syntax constraint, this needs to be hacked around with a
third overload:
@overload
def foo(x: int = ... encoding: None = ...) -> str: ...
@overload
def foo(x: int, encoding: str) -> bytes: ... # for foo(123, "utf-8")
@overload
def foo(*, encoding: str) -> bytes: ... # for foo(encoding="utf-8")
Not only is this hard to read, real examples in typeshed are usually
more complex, with many arguments before or after the affected argument
or even multiple affected arguments. This often becomes too complex to
write or maintain. Here is one example from the wild:
https://github.com/python/typeshed/blob/b95b729b9e07ab21d252701af0f5b7404672b952/stubs/redis/redis/client.pyi#L51
Allowing non-default arguments after default arguments would solve both
use cases above and eliminates a special case. I'm also not sure what
exactly the current SyntaxError really protects us from. Adding a
non-default after a default argument can't really lead bugs.
- Sebastian
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at
https://mail.python.org/archives/list/python-dev@python.org/message/7W5RMJVBRN6NE6A3LUP5Y4BMOZKQRYWH/
Code of Conduct: http://python.org/psf/codeofconduct/