[Python-ideas] keyword arguments everywhere (stdlib) - issue8706

Nick Coghlan ncoghlan at gmail.com
Sun Mar 4 04:46:33 CET 2012


On Sun, Mar 4, 2012 at 6:39 AM, Guido van Rossum <guido at python.org> wrote:
> Yeah, so it does make sense to standardize on a solution for this. Let
> it be @positional(N). Can you file an issue?

How could that even work?

Consider the following subset of the Mapping API:

    class C:

        @positional(2):
        def __init__(self, data=None, **kwds):
            self._stored_data = stored = {}
            if data is not None:
                stored.update(data)
            stored.update(kwds)

        @positional(2):
        def update(self, data=None, **kwds):
            stored = self._stored_data
            if data is not None:
                stored.update(data)
            stored.update(kwds)


Without gross hacking of the function internals, there's no way for a
decorator to make the following two calls work properly:

    x = C(self=5, data=10)
    x.update(self=10, data=5)

Both will complain about duplicate "self" and "data" arguments, unless
the "positional" decorator truly rips the function definition apart
and creates a new one that alters how the interpreter maps arguments
to parameters.

As Simon Sapin pointed out, the most correct way to write such code
currently is to accept *args and unpack it manually, which is indeed
exactly how the Mapping ABC implementation currently works [1]. While
the Mapping implementation doesn't currently use it, one simple way to
write such code is to use a *second* parameter binding step like this:

    class C:

        def _unpack_args(self, data=None):
            return self, data

        def __init__(*args, **kwds):
            self, data = C._unpack_args(*args)
            self._stored_data = stored = {}
            if data:
                stored.update(data)
            stored.update(kwds)

        def update(*args, **kwds):
            self, data = C._unpack_args(*args)
            stored = self._stored_data
            if data is not None:
                stored.update(data)
            stored.update(kwds)

The downside, of course, is that the error messages that come out of
such a binding operation may be rather cryptic (which is why the
Mapping ABC instead uses manual unpacking - so it can generate nice
error messages)

The difficulty of implementing the Mapping ABC correctly in pure
Python is the poster child for why the lack of positional-only
argument syntax is a language wart - we define APIs (in C) that work
that way, which people then have to reconstruct manually in Python.

My proposal is that we simply added a *third* alternative for "*args":
a full function parameter specification to be used to bind the
positional-only arguments.

That is:

1. '*args' collects the additional positional arguments and places
them in a tuple
2. '*' disallows any further positional arguments.
3. '*(SPEC)' binds the additional positional arguments according to
the parameter specification.

In all 3 cases, any subsequent parameter defintions are keyword only.

The one restriction placed on the nested SPEC is that it would only
allow "*args" at the end. The keyword only argument and positional
only argument forms would not be allowed, since they would make no
sense (as all arguments to the inner parameter binding operation are
positional by design).

Then the "_unpack_args" hack above would be unnecessary, and you could
just write:

    class C:

        def __init__(*(self, data=None), **kwds):
            self._stored_data = stored = {}
            if data:
                stored.update(data)
            stored.update(kwds)

        def update(*(self, data=None), **kwds):
            stored = self._stored_data
            if data is not None:
                stored.update(data)
            stored.update(kwds)

The objection was raised that this runs counter to the philosophy
behind PEP 3113 (which removed tuple unpacking from function
signatures). I disagree:
- this is not tuple unpacking, it is parameter binding
- it does not apply to arbitrary arguments, solely to the "extra
arguments" parameter, which is guaranteed to be a tuple
- it allows positional-only arguments to be clearly expressed in the
function signature, allowing the *interpreter* to deal with the
creation of nice error messages
- it *improves* introspection, since the binding of positional only
arguments is now expressed clearly in the function header (and
associated additional metadata on the function object), rather than
being hidden inside the function implementation

Regards,
Nick.

[1] http://hg.python.org/cpython/file/e67b3a9bd2dc/Lib/collections/abc.py#l511

-- 
Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia



More information about the Python-ideas mailing list