From 40e6f825cd60f49fe10055f647e29ed191b0bb76 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 12 Jul 2013 18:10:56 -0400 Subject: [PATCH] Add PEP 448: Additional Unpacking Generalizations by Joshua Landau. --- pep-0448.txt | 247 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100644 pep-0448.txt diff --git a/pep-0448.txt b/pep-0448.txt new file mode 100644 index 000000000..a9ad414f6 --- /dev/null +++ b/pep-0448.txt @@ -0,0 +1,247 @@ +PEP: 448 +Title: Additional Unpacking Generalizations +Version: $Revision$ +Last-Modified: $Date$ +Author: Joshua Landau +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 +operator to allow unpacking in more positions, an arbitrary number of +times, and in several additional circumstances. + +Specifically: + +Arbitrarily positioned unpacking operators:: + + >>> print(*[1], *[2], 3) + 1 2 3 + >>> dict(**{'x': 1}, y=3, **{'z': 2}) + {'x': 1, 'y': 2, 'z': 3} + +Function calls currently have the restriction that keyword arguments +must follow positional arguments and ``**`` unpackings must additionally +follow ``*`` unpackings. Because of the new levity for ``*`` and ``**`` +unpackings, it may be advisable to lift some or all of these +restrictions. + +As currently, if an argument is given multiple times - such as a +positional argument given both positionally and by keyword - a +TypeError is raised. + +Unpacking is proposed to be allowed inside tuples, lists, sets, +dictionaries and comprehensions:: + + >>> *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 +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 +2D_list for i in j]``, as the more readable ``[*l for l in 2D_list]``. +Other uses are possible, but expected to occur rarely. + + +Specification +============= + +Function calls may accept an unbound number of ``*`` and ``**`` +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. + +Function calls currently have the restriction that keyword arguments +must follow positional arguments and ``**`` unpackings must additionally +follow ``*`` unpackings. Because of the new levity for ``*`` and ``**`` +unpackings, it may be advisable to list some or all of these +restrictions. + +As currently, if an argument is given multiple times - such as a +positional argument given both positionally and by keyword - a +TypeError is raised. + +If the restrictions are kept, a function call will look like this:: + + function( + argument or *args, argument or *args, ..., + kwargument or *args, kwargument or *args, ..., + kwargument or **kwargs, kwargument or **kwargs, ... + ) + +If they are removed completely, a function call will look like this:: + + function( + argument or keyword_argument or `*`args or **kwargs, + argument or keyword_argument or `*`args or **kwargs, + ... + ) + + +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. + +Examples include:: + + {*[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} + + +Disadvantages +============= + +If the current restrictions for function call arguments (keyword +arguments must follow positional arguments and ``**`` unpackings must +additionally follow ``*`` unpackings) are kept, the allowable orders +for arguments in a function call is more complicated than before. +The simplest explanation for the rules may be "positional arguments +come first and keyword arguments follow, but ``*`` unpackings are +allowed after keyword arguments". + +If the current restrictions are lifted, there are no obvious gains to +code as the only new orders that are allowed look silly: ``f(a, e=e, +d=d, b, c)`` being a simpler example. + + +Whilst ``*elements, = iterable`` causes ``elements`` to be a list, +``elements = *iterable,`` causes ``elements`` to be a tuple. The +reason for this may not be obvious at first glance and may confuse +people unfamiliar with the construct. + + +Implementation +============== + +An implementation for an old version of Python 3 is found at Issue +2292 on bug tracker [1]_, although several changes should be made: + +- It has yet to be updated to the most recent Python version + +- It features a now redundant replacement for "yield from" which + should be removed + +- It also loses support for calling function with keyword arguments before + positional arguments, which is an unnecessary backwards-incompatible change + +- If the restrictions on the order of arguments in a function call are + partially or fully lifted, they would need to be included + + +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: