This proposal is an alternative to Rodrigo's "Keyword arguments 
self-assignment" thread.

Rodrigo, please feel free to mine this for useful nuggets in your PEP.

(I don't claim to have invented the syntax -- I think it might have been 
Alex Hall?)


Keyword Unpacking Shortcut
--------------------------

Inside function calls, the syntax

    **{identifier [, ...]}

expands to a set of `identifier=identifier` argument bindings.

This will be legal anywhere inside a function call that keyword 
unpacking would be legal. That means that syntactically it will legal to 
mix this between other keyword arguments:

    # currently legal
    func(z=5, **d1, x=0, y=6, **d2, w=7)

    # propose to allow Keyword Unpacking Shortcut as well
    func(z=5, **d1, x=0, **{u, v}, y=6, **d2, w=7)

    # which would be equivalent to:
    func(z=5, **d1, x=0, u=u, v=v, y=6, **d2, w=7)


The order of unpacking should be the same as the order of unpacking 
regular dict unpacking:

    func(**{a, b, c})
    func(**{'a': a, 'b': b, 'c': c})

should unpack the parameters in the same order.


Interpretation
--------------

There are at least two ways to interpret the shortcut:

1.  It is like unpacking a dict where only the keys (identifiers) are 
given, and the values are implied to be exactly the same as the keys.

2.  It is like unpacking a dict formed from a set of identifiers, used 
as both the parameter name and evaluated as the argument value.

(This does not imply that the shortcut need actually generate either a 
dict or a set. The actual implementation will depend on the compiler.)

Either way, the syntax can be justified:

* Why use double-star `**`? Because it is a form of dict unpacking.

* Why use curly brackets (braces) `{}`? It's a set of identifiers to be 
auto-filled with values matching the identifier.

Put them together and you get `**{ ... }` as the syntax.



Examples
--------

The function call:

    function(**{meta, invert, dunder, private, ignorecase})

expands to:

    function(meta=meta,
             invert=invert,
             dunder=dunder,
             private=private,
             ignorecase=ignorecase
             )


Because the keyword unpacking shortcut can be used together with other 
unpacking operators, it can be (ab)used for quite complex function 
calls. `Popen` has one of the most complex signatures in the Python 
standard library:

https://docs.python.org/3.8/library/subprocess.html#subprocess.Popen

Here is an example of mixing positional and keyword arguments together 
with the proposed keyword unpacking shortcut in a single call to Popen.


    subprocess.Popen(
        # Positional arguments.
        args, bufsize, executable,
        *stdfiles,

        # Keyword arguments.
        shell=True, close_fds=False, cwd=where, creationflags=flags,
        env={'MYVAR': 'some value'},
        **textinfo,

        # Keyword unpacking shortcut.
        **{preexec_fn, startupinfo, restore_signals, pass_fds,
           universal_newlines, start_new_session}
        )

which will expand to:


    subprocess.Popen(
        # Positional arguments.
        args, bufsize, executable,
        *stdfiles,

        # Keyword arguments.
        shell=True, close_fds=False, cwd=where, creationflags=flags,
        env={'MYVAR': 'some value'},
        **textinfo,

        # Keyword unpacking shortcut expands to:
        preexec_fn=preexec_fn,
        startupinfo=startupinfo,
        restore_signals=restore_signals,
        pass_fds=pass_fds,
        universal_newlines=universal_newlines,
        start_new_session=start_new_session
        )


Note that plain keyword arguments such as:

    cwd=where

have the advantage that the key and value are shown directly in the 
function call, but can be excessively verbose when there are many of 
them, especially when the argument value duplicates the parameter name, 
e.g. `start_new_session=start_new_session` etc.

On the other hand, plain keyword unpacking:

    **textinfo

is terse, but perhaps too terse. Neither the keys nor the values are 
immediately visible. Instead, one must search the rest of the function 
or module for the definition of `textinfo` to learn which parameters are 
being filled in.

The keyword unpacking shortcut takes a middle ground. Like explicit 
keyword arguments, and unlike the regular `**textinfo` unpacking, the 
parameter names are visible in the function call. But it avoids the 
repetition and redundancy of repeating the parameter name as in 
`start_new_session=start_new_session`.


Backwards compatibility
-----------------------

The syntax is not currently legal so there are no backwards 
compatibility concerns.



Possible points of confusion
----------------------------

Sequence unpacking is currently allowed for sets:

    func(*{a, b, c})  # Unpacks a, b, c in arbitrary order.

Anyone intending to use the keyword unpacking shortcut but accidentally 
writing a single star instead of a double will likely end up with 
confusing failures rather than an explicit exception. However, the same 
applies to dict unpacking:

    func(*{'meta': True', 'invert': False, 'dunder': True})

which will unpack the keys as positional arguments. Experience strongly 
suggests this is a rare error in practice, and there is little reason to 
expect that keywords unpacking shortcut will be any different. This 
could be handled by a linter.


-- 
Steven
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/GXQUERT5ZO2ENRHPOGIVVJLRF6BDZVT4/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to