Rough sketch of a PEP for issue2292

Joshua Landau joshua.landau.ws at gmail.com
Sat Jun 29 21:35:20 EDT 2013


In order to get the ball rolling, and because after hours of futzing I
still can't get the diff to work (yeah, fine, I'm incompetent), I've
started sketching out how a PEP for http://bugs.python.org/issue2292,
"Missing *-unpacking generalizations" might look.

It's attached if anyone cares to look. You can insult me over it if
you want, but I'd prefer if you liked it :P. I also don't mind
additions to it if you feel you want to waste some time.

If anyone knows how to get the patch (from the bug report) working, or
where to find
http://code.python.org/python/users/twouters/starunpack after
code.python.org was deleted in favour of hg.python.org (which seems
not to have it), that'd be nice too.

Again, this is a sketch. It's incomplete and I'm likely to replace
large parts of it tomorrow. There's also very little justification and, I
think, there are too many code blocks. So it's all liable to change.
-------------- next part --------------
PEP: XXX
Title: Additional Unpacking Generalizations
Version: $Revision$
Last-Modified: $Date$
Author: Joshua Landau <joshua at landau.ws>
Discussions-To: python-ideas at python.org
Status: Draft
Type: Standards Track
Content-Type: text/x-rst
Created: 29-Jun-2013
Python-Version: 3.4
Post-History: #TODO


Abstract
========

This PEP proposes extended usages of the ``*`` iterable unpacking operator.

Specifically:

Multiple unpackings::

    >>> print(*[1], *[2])
    1 2
    >>> dict(**{'x': 1}, **{'y': 2})
    {'x': 1, 'y': 2}

Unpacking does not prevent further arguments being passed::

    >>> print(*[1], 2)
    1 2
    >>> dict(**{'x': 1}, y=2)
    {'x': 1, 'y': 2}
    >>> def f(*args, last): pass

Keywords arguments must still follow positional arguments but now must also follow ``*``-unpackings. The function of a lone ``*`` in function definitions is unchanged.

Unpacking inside tuples, lists, sets and dictionaries, and comprehensions for iterators, lists, sets and dictionaries::

    >>> *range(4), 4
    (0, 1, 2, 3, 4)
    >>> [*range(4), 4]
    [0, 1, 2, 3, 4]
    >>> {*range(4), 4}
    {0, 1, 2, 3, 4}
    >>> {'x': 1, **{'y': 2}}
    {'x': 1, 'y': 2}

    >>> ranges = [range(i) for i in range(5)]

    >>> [*item for item in ranges]
    [0, 0, 1, 0, 1, 2, 0, 1, 2, 3]


Rationale
=========

Current usage of the ``*`` iterable unpacking operator features somewhat arbitrary restrictions.

A limitation of one unpacking per function call makes some function calls more verbose than necessary; instead of::

    function(**arguments, **more_arguments)

one is forced to write::

    kwargs = arguments.copy()
    kwargs.update(more_arguments)
    function(**kwargs)

or, if they know to do so::

    from collections import ChainMap
    function(**ChainMap(more_arguments, arguments))

This also applies to any circumstance where you would like to unpack positional arguments followed by another positional argument::

    function(*args, arg)


Function definitions are also now more symmetrical with assignment; whereas previously just::

    first, *others, last = iterable

was valid, now so too is::

    def f(first, *others, last):
        ...

    f(*iterable)


There are two primary rationale for unpacking inside of containers. Firstly, it would make sense for::

    lst = (1, 2, 3, 4, 5)
    first, *others, last = lst

to be the inverse of::

    first, others, last = 1, [2, 3, 4], 5
    lst = first, *others, last


Secondly, it vastly simplifies dictionary "addition" such as::

    combination = first_dictionary.copy()
    combination.update({"x": 1, "y": 2})

and equivalents, as now you can just write::

    combination = {**first_dictionary, "x": 1, "y": 2}

which is especially important in contexts where expressions are preferred. This can also help replace ``lst + [item]``.


Specification
=============

This isn't my forté, so it will take a bit longer.

Function calls may accept an unbound number of ``*`` and ``**`` unpackings, which are allowed anywhere that positional and keyword arguments are allowed respectively. In approximate pseudo-notation:

::

    function_call(
        [(*args|arg), ]...
        [(**kwargs|kwarg=<expr>), ]...
    )

The function ``lambda *args, last: ...`` now does not require ``last`` to be a keyword only argument, and thus::

    def func(*args, *, keyword_only):
        ...

is valid. Otherwise, function definitions remain unchanged.


Tuples, lists, sets and dictionaries now allow unpacking. Dictionaries require ``**`` unpacking, all the others require ``*`` unpacking. A dictionary's key remain in a right-to-left priority order, so ``{**{'a': 1}, 'a': 2, **{'a': 3}}`` evaluates to ``{'a': 3}``.


**I am unclear on what the definition for comprehensions should be: should** ``{**d for d in dicts}`` **work as well as** ``{*s for s in sets}`` **par exemple?**


Backwards-Incompatibility
=========================

Parts of this change are not backwards-compatible.

- ``function(kwarg="foo", *args)`` is no longer valid syntax; ``function(*args, kwarg="foo")`` is required instead

- ``lambda *args, last: ...`` no longer requires ``last`` to be a keyword only argument


**I don't feel I have the standing to make a judgment on these cases. Needless to say the first of these is a more significant hurdle and will affect more working code.**


Implementation
==============

**No idea. I know nothing about the implementation code.**


Discussion
==========

**This is quite old; any help finding the source and areas of discussion will be appreciated.**


References
==========

**Ditto**


Copyright
=========

This document has been placed in the public domain.


More information about the Python-list mailing list