2013-07-12 18:10:56 -04:00
|
|
|
|
PEP: 448
|
|
|
|
|
Title: Additional Unpacking Generalizations
|
|
|
|
|
Version: $Revision$
|
|
|
|
|
Last-Modified: $Date$
|
|
|
|
|
Author: Joshua Landau <joshua@landau.ws>
|
|
|
|
|
Discussions-To: python-ideas@python.org
|
|
|
|
|
Status: Draft
|
|
|
|
|
Type: Standards Track
|
|
|
|
|
Content-Type: text/x-rst
|
|
|
|
|
Created: 29-Jun-2013
|
|
|
|
|
Python-Version: 3.4
|
|
|
|
|
Post-History:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Abstract
|
|
|
|
|
========
|
|
|
|
|
|
|
|
|
|
This PEP proposes extended usages of the ``*`` iterable unpacking
|
2015-02-02 08:31:33 -05:00
|
|
|
|
operator and ``**`` dictionary unpacking operators
|
2015-01-30 15:02:45 -05:00
|
|
|
|
to allow unpacking in more positions, an arbitrary number of
|
2015-02-02 08:31:33 -05:00
|
|
|
|
times, and in additional circumstances. Specifically,
|
|
|
|
|
in function calls, in comprehensions and generator expressions, and
|
|
|
|
|
in displays.
|
2013-07-12 18:10:56 -04:00
|
|
|
|
|
2015-01-30 15:02:45 -05:00
|
|
|
|
Function calls are proposed to support an arbitrary number of
|
|
|
|
|
unpackings rather than just one::
|
2013-07-12 18:10:56 -04:00
|
|
|
|
|
|
|
|
|
>>> print(*[1], *[2], 3)
|
|
|
|
|
1 2 3
|
2013-08-13 17:04:22 -04:00
|
|
|
|
>>> dict(**{'x': 1}, y=2, **{'z': 3})
|
2013-07-12 18:10:56 -04:00
|
|
|
|
{'x': 1, 'y': 2, 'z': 3}
|
|
|
|
|
|
2015-01-30 15:02:45 -05:00
|
|
|
|
Unpacking is proposed to be allowed inside tuple, list, set,
|
|
|
|
|
and dictionary displays::
|
2013-07-12 18:10:56 -04:00
|
|
|
|
|
|
|
|
|
>>> *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}
|
|
|
|
|
|
2015-02-02 08:31:33 -05:00
|
|
|
|
In dictionaries, later values will always override earlier ones::
|
2015-01-30 15:02:45 -05:00
|
|
|
|
|
|
|
|
|
>>> {'x': 1, **{'x': 2}}
|
|
|
|
|
{'x': 2}
|
|
|
|
|
|
|
|
|
|
>>> {**{'x': 2}, 'x': 1}
|
|
|
|
|
{'x': 1}
|
|
|
|
|
|
|
|
|
|
Unpacking is proposed to be allowed inside list, set,
|
|
|
|
|
and dictionary comprehensions::
|
|
|
|
|
|
2013-07-12 18:10:56 -04:00
|
|
|
|
>>> ranges = [range(i) for i in range(5)]
|
|
|
|
|
>>> [*item for item in ranges]
|
|
|
|
|
[0, 0, 1, 0, 1, 2, 0, 1, 2, 3]
|
|
|
|
|
|
2015-01-30 15:02:45 -05:00
|
|
|
|
>>> {*item for item in ranges}
|
|
|
|
|
{0, 1, 2, 3}
|
|
|
|
|
|
2013-07-12 18:10:56 -04:00
|
|
|
|
|
|
|
|
|
Rationale
|
|
|
|
|
=========
|
|
|
|
|
|
|
|
|
|
Current usage of the ``*`` iterable unpacking operator features
|
|
|
|
|
unnecessary restrictions that can harm readability.
|
|
|
|
|
|
|
|
|
|
Unpacking multiple times has an obvious rationale. When you want to
|
|
|
|
|
unpack several iterables into a function definition or follow an unpack
|
|
|
|
|
with more positional arguments, the most natural way would be to write::
|
|
|
|
|
|
|
|
|
|
function(**kw_arguments, **more_arguments)
|
|
|
|
|
|
|
|
|
|
function(*arguments, argument)
|
|
|
|
|
|
|
|
|
|
Simple examples where this is useful are ``print`` and ``str.format``.
|
|
|
|
|
Instead, you could be forced to write::
|
|
|
|
|
|
|
|
|
|
kwargs = dict(kw_arguments)
|
|
|
|
|
kwargs.update(more_arguments)
|
|
|
|
|
function(**kwargs)
|
|
|
|
|
|
|
|
|
|
args = list(arguments)
|
|
|
|
|
args.append(arg)
|
|
|
|
|
function(*args)
|
|
|
|
|
|
|
|
|
|
or, if you know to do so::
|
|
|
|
|
|
|
|
|
|
from collections import ChainMap
|
|
|
|
|
function(**ChainMap(more_arguments, arguments))
|
|
|
|
|
|
|
|
|
|
from itertools import chain
|
|
|
|
|
function(*chain(args, [arg]))
|
|
|
|
|
|
|
|
|
|
which add unnecessary line-noise and, with the first methods, causes
|
|
|
|
|
duplication of work.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
There are two primary rationales for unpacking inside of containers.
|
|
|
|
|
Firstly there is a symmetry of assignment, where ``fst, *other, lst =
|
|
|
|
|
elems`` and ``elems = fst, *other, lst`` are approximate inverses,
|
|
|
|
|
ignoring the specifics of types. This, in effect, simplifies the
|
|
|
|
|
language by removing special cases.
|
|
|
|
|
|
|
|
|
|
Secondly, it vastly simplifies types of "addition" such as combining
|
|
|
|
|
dictionaries, and does so in an unambiguous and well-defined way::
|
|
|
|
|
|
|
|
|
|
combination = {**first_dictionary, "x": 1, "y": 2}
|
|
|
|
|
|
|
|
|
|
instead of::
|
|
|
|
|
|
|
|
|
|
combination = first_dictionary.copy()
|
|
|
|
|
combination.update({"x": 1, "y": 2})
|
|
|
|
|
|
|
|
|
|
which is especially important in contexts where expressions are
|
|
|
|
|
preferred. This is also useful as a more readable way of summing
|
|
|
|
|
iterables into a list, such as ``my_list + list(my_tuple) +
|
|
|
|
|
list(my_range)`` which is now equivalent to just ``[*my_list,
|
|
|
|
|
*my_tuple, *my_range]``.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The addition of unpacking to comprehensions is a logical extension.
|
|
|
|
|
It's usage will primarily be a neat replacement for ``[i for j in
|
2015-02-02 08:31:33 -05:00
|
|
|
|
list_of_lists for i in j]``, as the more readable
|
|
|
|
|
``[*l for l in list_of_lists]``. The iterable version,
|
|
|
|
|
``(*l for l in list_of_lists)``, replaces ``itertools.chain.from_iterable``.
|
2013-07-12 18:10:56 -04:00
|
|
|
|
Other uses are possible, but expected to occur rarely.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Specification
|
|
|
|
|
=============
|
|
|
|
|
|
2015-01-30 15:02:45 -05:00
|
|
|
|
Function calls may accept an unbounded number of ``*`` and ``**``
|
2013-07-12 18:10:56 -04:00
|
|
|
|
unpackings. There will be no restriction of the order of positional
|
|
|
|
|
arguments with relation to ``*`` unpackings nor any restriction of the
|
|
|
|
|
order of keyword arguments with relation to ``**`` unpackings.
|
|
|
|
|
|
2015-02-24 19:44:25 -05:00
|
|
|
|
Function calls continue to have the restriction that keyword arguments
|
2013-07-12 18:10:56 -04:00
|
|
|
|
must follow positional arguments and ``**`` unpackings must additionally
|
2015-01-30 15:02:45 -05:00
|
|
|
|
follow ``*`` unpackings.
|
2013-07-12 18:10:56 -04:00
|
|
|
|
|
2015-01-30 15:02:45 -05:00
|
|
|
|
Currently, if an argument is given multiple times — such as a
|
|
|
|
|
positional argument given both positionally and by keyword — a
|
|
|
|
|
``TypeError`` is raised. This remains true for duplicate arguments
|
2015-02-02 08:31:33 -05:00
|
|
|
|
provided through multiple ``**`` unpackings,
|
2015-01-30 15:02:45 -05:00
|
|
|
|
e.g. ``f(**{'x': 2}, **{'x': 3})``.
|
2013-07-12 18:10:56 -04:00
|
|
|
|
|
2015-01-30 15:02:45 -05:00
|
|
|
|
A function looks like this::
|
2013-07-12 18:10:56 -04:00
|
|
|
|
|
|
|
|
|
function(
|
2015-01-30 15:02:45 -05:00
|
|
|
|
argument or *args, argument or *args, ...,
|
|
|
|
|
kwargument or *args, kwargument or *args, ...,
|
|
|
|
|
kwargument or **kwargs, kwargument or **kwargs, ...
|
2013-07-12 18:10:56 -04:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
Tuples, lists, sets and dictionaries will allow unpacking. This will
|
|
|
|
|
act as if the elements from unpacked items were inserted in order at
|
|
|
|
|
the site of unpacking, much as happens in unpacking in a function-call.
|
|
|
|
|
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}``. There
|
|
|
|
|
is no restriction on the number or position of unpackings.
|
|
|
|
|
|
|
|
|
|
Comprehensions, by simple extension, will support unpacking. As before,
|
|
|
|
|
dictionaries require ``**`` unpacking, all the others require ``*``
|
|
|
|
|
unpacking and key priorities are unchanged.
|
|
|
|
|
|
2015-01-30 15:02:45 -05:00
|
|
|
|
For example::
|
2013-07-12 18:10:56 -04:00
|
|
|
|
|
|
|
|
|
{*[1, 2, 3], 4, 5, *{6, 7, 8}}
|
|
|
|
|
|
|
|
|
|
(*e for e in [[1], [3, 4, 5], [2]])
|
|
|
|
|
|
|
|
|
|
{**dictionary for dictionary in (globals(), locals())}
|
|
|
|
|
|
|
|
|
|
{**locals(), "override": None}
|
|
|
|
|
|
2015-02-02 08:31:33 -05:00
|
|
|
|
Unbracketed comprehensions in function calls, such as ``f(x for x in it)``,
|
|
|
|
|
are already valid. These could be extended to::
|
|
|
|
|
|
|
|
|
|
f(*x for x in it) == f((*x for x in it))
|
|
|
|
|
f(**x for x in it) == f({**x for x in it})
|
|
|
|
|
|
|
|
|
|
However, this is likely to be confusing and is not included in this
|
|
|
|
|
PEP. These will throw ``SyntaxError`` and comprehensions with explicit
|
|
|
|
|
brackets should be used instead.
|
2015-01-30 15:02:45 -05:00
|
|
|
|
|
2013-07-12 18:10:56 -04:00
|
|
|
|
|
|
|
|
|
Disadvantages
|
|
|
|
|
=============
|
|
|
|
|
|
2015-02-02 08:31:33 -05:00
|
|
|
|
The allowable orders for arguments in a function call are more
|
|
|
|
|
complicated than before. The simplest explanation for the rules
|
|
|
|
|
may be "positional arguments precede keyword arguments and ``**``
|
|
|
|
|
unpacking; ``*`` unpacking precedes ``**`` unpacking".
|
2013-07-12 18:10:56 -04:00
|
|
|
|
|
2015-02-02 08:31:33 -05:00
|
|
|
|
Whilst ``*elements, = iterable`` causes ``elements`` to be a list,
|
2013-07-12 18:10:56 -04:00
|
|
|
|
``elements = *iterable,`` causes ``elements`` to be a tuple. The
|
2015-01-30 15:02:45 -05:00
|
|
|
|
reason for this may confuse people unfamiliar with the construct.
|
2013-07-12 18:10:56 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Implementation
|
|
|
|
|
==============
|
|
|
|
|
|
2015-01-30 15:02:45 -05:00
|
|
|
|
An implementation for Python 3.5 is found at Issue 2292 on bug tracker [1]_.
|
2013-07-12 18:10:56 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
References
|
|
|
|
|
==========
|
|
|
|
|
|
|
|
|
|
.. [1] Issue 2292, "Missing `*`-unpacking generalizations", Thomas Wouters
|
|
|
|
|
(http://bugs.python.org/issue2292)
|
|
|
|
|
|
|
|
|
|
.. [2] Discussion on Python-ideas list,
|
|
|
|
|
"list / array comprehensions extension", Alexander Heger
|
|
|
|
|
(http://mail.python.org/pipermail/python-ideas/2011-December/013097.html)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Copyright
|
|
|
|
|
=========
|
|
|
|
|
|
|
|
|
|
This document has been placed in the public domain.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
..
|
|
|
|
|
Local Variables:
|
|
|
|
|
mode: indented-text
|
|
|
|
|
indent-tabs-mode: nil
|
|
|
|
|
sentence-end-double-space: t
|
|
|
|
|
fill-column: 70
|
|
|
|
|
coding: utf-8
|
|
|
|
|
End:
|