On 09. 11. 21 10:50, Chris Angelico wrote:
On Tue, Nov 9, 2021 at 8:38 PM Sebastian Rittau <srit...@rittau.biz> wrote:

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.


What would this mean, though:

foo(2)

Is that legal? If it is, it has to be the same as foo(y=2), by your
definition. But that would mean that it's hard to get your head around
the mapping of arguments and parameters.

foo(1, 2) # x=1, y=2
foo(1) # x=None, y=1

There are a very very few functions in Python that have this sort of
odd behaviour (range() being probably the only one most programmers
will ever come across), and it's not something to encourage.

A more extreme case is functions with an optional *group*. In curses, the first two arguments to addch are optional. In `help()` it's documented as `window.addch([y, x,] ch[, attr=...])` and you can call it as one of:

window.addch(ch)
window.addch(ch, attr)
window.addch(y, x, ch)
window.addch(y, x, ch, attr)

see: https://docs.python.org/3/library/curses.html#curses.window.addch

Supporting this was a headache for Argument Clinic (PEP 436), and AFAIK it still isn't possible to express this as an inspect.Signature (PEP 362).

Allowing non-default arguments after default arguments would mean introspection tools (and code that uses them) would need to be changed to prepare for the new possibilities. It's not free.


And for the "encoding" case: IMO, varying the return type based on an optional "encoding" argument" is a holdover from the pre-typing era, when return types were only specified in the documentation -- just like "addch" is a holdover from the days when function signatures were only described in the docs. Nowadays, I'd consider it bad API design. The @overloads are ugly but they work -- just like the API itself. IMO we shouldn't add special cases to encourage more of it.

I would instead recommend making the parameters keyword-only, which
would allow any of them to have defaults or not have defaults. In
terms of useful API design, this is usually more helpful than having
an early parameter omitted.

+1. I'm not sure if it's possible to mark args as keyword-only in the type stubs while keeping actual implementation backwards-compatible, but if it is, it might be a good option.
_______________________________________________
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/JIK7EJLN4WG4EJE4UVYVLUORGQB6XQWR/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to