Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e3e3865
make genexp rule match the others in form (why was this hard-coded?)
adqm Jun 13, 2025
b2ecf34
basic grammar changes to allow starred expressions in comprehensions
adqm Jun 13, 2025
9abf678
working (?) starred expressions for genexp, listcomp, setcomp
adqm Jun 13, 2025
0e0e5cf
add basic implementation of unpacking dictcomp
adqm Jun 13, 2025
57e1e2b
support for async comprehensions?
adqm Jun 13, 2025
f5cc50a
remove forced syntax errors for formerly-bad comprehensions (we may w…
adqm Jun 13, 2025
2fb2a9e
add specific error messages for trying to use dict unpacking in a lis…
adqm Jun 15, 2025
2c8bd0a
commit generated parser files
adqm Jun 17, 2025
5bf22de
change async generator code to do an explicit for loop (yield from wo…
adqm Jun 23, 2025
d0d138b
fix for grammar to preserve correct syntax error reporting
adqm Jun 23, 2025
650a13b
add specific error messages for more potentially-common syntax errors
adqm Jun 23, 2025
21e5bc2
add/change test cases to account for unpacking in comprehensions
adqm Jun 23, 2025
30779b1
another couple of quick tests involving walrus operators
adqm Jun 24, 2025
bb064c3
first pass at adding documentation for comprehension unpacking
adqm Jun 24, 2025
0e68154
try to follow pep7
adqm Jun 25, 2025
b4c6fc7
improve comment and simplify phrasing of genexp grammar in the docume…
adqm Jun 26, 2025
a8d390e
attempt at better error messages for * and ** misuse
adqm Jun 28, 2025
69a81d9
remove unused grammar rule
adqm Jun 29, 2025
a68dcf1
for consistency, check for genexp before tuple
adqm Jun 29, 2025
79e581b
try to improve and unify error reporting about unpacking in comprehen…
adqm Jun 29, 2025
3f97543
add a few additional test cases
adqm Jul 4, 2025
c5cc3c8
typo in itertools.chain example in the docs
adqm Jul 14, 2025
1a81dbb
change capitalization of error message
adqm Jul 20, 2025
e1f3f9a
add a comment
adqm Aug 14, 2025
a2b7a69
change unpacking semantics for genexps: no delegation
adqm Aug 15, 2025
07dc05f
roll back change to genexp semantics
adqm Aug 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Doc/library/itertools.rst
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,13 @@ loops that truncate the stream.
for iterable in iterables:
yield from iterable

.. note::

Since version 3.15, the following is also roughly equivalent::

def chain(*iterables):
return (*iterable for iterable in iterables)


.. classmethod:: chain.from_iterable(iterable)

Expand All @@ -219,6 +226,13 @@ loops that truncate the stream.
for iterable in iterables:
yield from iterable

.. note::

Since version 3.15, the following is also roughly equivalent::

def from_iterable(iterables):
return (*iterable for iterable in iterables)


.. function:: combinations(iterable, r)

Expand Down
46 changes: 34 additions & 12 deletions Doc/reference/expressions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -209,17 +209,18 @@ called "displays", each of them in two flavors:
Common syntax elements for comprehensions are:

.. productionlist:: python-grammar
comprehension: `assignment_expression` `comp_for`
comprehension: `flexible_expression` `comp_for`
comp_for: ["async"] "for" `target_list` "in" `or_test` [`comp_iter`]
comp_iter: `comp_for` | `comp_if`
comp_if: "if" `or_test` [`comp_iter`]

The comprehension consists of a single expression followed by at least one
:keyword:`!for` clause and zero or more :keyword:`!for` or :keyword:`!if` clauses.
In this case, the elements of the new container are those that would be produced
by considering each of the :keyword:`!for` or :keyword:`!if` clauses a block,
nesting from left to right, and evaluating the expression to produce an element
each time the innermost block is reached.
:keyword:`!for` clause and zero or more :keyword:`!for` or :keyword:`!if`
clauses. In this case, the elements of the new container are those that would
be produced by considering each of the :keyword:`!for` or :keyword:`!if`
clauses a block, nesting from left to right, and evaluating the expression to
produce an element each time the innermost block is reached. If the expression
is starred, the result will instead be unpacked to produce 0 or more elements.

However, aside from the iterable expression in the leftmost :keyword:`!for` clause,
the comprehension is executed in a separate implicitly nested scope. This ensures
Expand All @@ -232,6 +233,11 @@ leftmost :keyword:`!for` clause cannot be evaluated in the enclosing scope as
they may depend on the values obtained from the leftmost iterable. For example:
``[x*y for x in range(10) for y in range(x, x+10)]``.

.. note::
If any of the expressions in a comprehension contain a walrus operator (``:=``),
the resulting variable binding happens in the enclosing scope. See
:pep:`572` for more details and examples.

To ensure the comprehension always results in a container of the appropriate
type, ``yield`` and ``yield from`` expressions are prohibited in the implicitly
nested scope.
Expand Down Expand Up @@ -264,6 +270,9 @@ See also :pep:`530`.
asynchronous functions. Outer comprehensions implicitly become
asynchronous.

.. versionchanged:: 3.15
Unpacking with the ``*`` operator is now allowed in the expression.


.. _lists:

Expand Down Expand Up @@ -339,8 +348,8 @@ enclosed in curly braces:
.. productionlist:: python-grammar
dict_display: "{" [`dict_item_list` | `dict_comprehension`] "}"
dict_item_list: `dict_item` ("," `dict_item`)* [","]
dict_comprehension: `dict_item` `comp_for`
dict_item: `expression` ":" `expression` | "**" `or_expr`
dict_comprehension: `expression` ":" `expression` `comp_for`

A dictionary display yields a new dictionary object.

Expand All @@ -362,10 +371,21 @@ earlier dict items and earlier dictionary unpackings.
.. versionadded:: 3.5
Unpacking into dictionary displays, originally proposed by :pep:`448`.

A dict comprehension, in contrast to list and set comprehensions, needs two
expressions separated with a colon followed by the usual "for" and "if" clauses.
When the comprehension is run, the resulting key and value elements are inserted
in the new dictionary in the order they are produced.
A dict comprehension may take one of two forms:

The first form uses two expressions separated with a colon followed by the
usual "for" and "if" clauses. When the comprehension is run, the resulting key
and value elements are inserted in the new dictionary in the order they are
produced.

The second form uses a single expression prefixed by the ``**`` dictionary
unpacking operator followed by the usual "for" and "if" clauses. When the
comprehension is run, the expression is evaluated and then unpacked, inserting
0 or more key/value pairs into the new dictionary.

Both forms of dictionary comprehension retain the property that if the same key
is specified multiple times, the associated value in the resulting dictionary
will be the last one specified.

.. index:: pair: immutable; object
hashable
Expand All @@ -382,6 +402,8 @@ prevails.
the key. Starting with 3.8, the key is evaluated before the value, as
proposed by :pep:`572`.

.. versionchanged:: 3.15
Unpacking with the ``**`` operator is now allowed in dictionary comprehensions.

.. _genexpr:

Expand All @@ -396,7 +418,7 @@ Generator expressions
A generator expression is a compact generator notation in parentheses:

.. productionlist:: python-grammar
generator_expression: "(" `expression` `comp_for` ")"
generator_expression: "(" `starred_expression` `comp_for` ")"

A generator expression yields a new generator object. Its syntax is the same as
for comprehensions, except that it is enclosed in parentheses instead of
Expand Down
19 changes: 19 additions & 0 deletions Doc/tutorial/classes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -926,6 +926,25 @@ Examples::
>>> list(data[i] for i in range(len(data)-1, -1, -1))
['f', 'l', 'o', 'g']

>>> x = [[1,2,3], [], [4, 5]]
>>> g = (*i for i in x)
>>> list(g)
[1, 2, 3, 4, 5]

In most cases, generator expressions must be wrapped in parentheses. As a
special case, however, when provided as the sole argument to a function (as in
the examples involving ``sum``, ``set``, ``max``, and ``list`` above), the
generator expression does not need to be wrapped in an additional set of
parentheses. That is to say, the following two pieces of code are semantically
equivalent::

>>> f(x for x in y)
>>> f((x for x in y))

as are the following::

>>> f(*x for x in y)
>>> f((*x for x in y))


.. rubric:: Footnotes
Expand Down
64 changes: 62 additions & 2 deletions Doc/tutorial/datastructures.rst
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,47 @@ The :func:`zip` function would do a great job for this use case::

See :ref:`tut-unpacking-arguments` for details on the asterisk in this line.

Unpacking in Lists and List Comprehensions
------------------------------------------

The section on :ref:`tut-unpacking-arguments` describes the use of ``*`` to
"unpack" the elements of an iterable object, providing each one seperately as
an argument to a function. Unpacking can also be used in other contexts, for
example, when creating lists. When specifying elements of a list, prefixing an
expression by a ``*`` will unpack the result of that expression, adding each of
its elements to the list we're creating::

>>> x = [1, 2, 3]
>>> [0, *x, 4, 5, 6]
[0, 1, 2, 3, 4, 5, 6]

This only works if the expression following the ``*`` evaluates to an iterable
object; trying to unpack a non-iterable object will raise an exception::

>>> x = 1
>>> [0, *x, 2, 3, 4]
Traceback (most recent call last):
File "<python-input-1>", line 1, in <module>
[0, *x, 2, 3, 4]
TypeError: Value after * must be an iterable, not int

Unpacking can also be used in list comprehensions, as a way to build a new list
representing the concatenation of an arbitrary number of iterables::

>>> x = [[1, 2, 3], [4, 5, 6], [], [7], [8, 9]]
>>> [*element for element in x]
[1, 2, 3, 4, 5, 6, 7, 8, 9]

Note that the effect is that each element from ``x`` is unpacked. This works
for arbitrary iterable objects, not just lists::

>>> x = [[1, 2, 3], 'cat', {'spam': 'eggs'}]
>>> [*element for element in x]
[1, 2, 3, 'c', 'a', 't', 'spam']

But if the objects in ``x`` are not iterable, this expression would again raise
an exception.

.. _tut-del:

The :keyword:`!del` statement
Expand Down Expand Up @@ -395,7 +436,10 @@ A tuple consists of a number of values separated by commas, for instance::
>>> v = ([1, 2, 3], [3, 2, 1])
>>> v
([1, 2, 3], [3, 2, 1])

>>> # they support unpacking just like lists:
>>> x = [1,2,3]
>>> 0, *x, 4
(0, 1, 2, 3, 4)

As you see, on output tuples are always enclosed in parentheses, so that nested
tuples are interpreted correctly; they may be input with or without surrounding
Expand Down Expand Up @@ -480,12 +524,16 @@ Here is a brief demonstration::
{'r', 'd', 'b', 'm', 'z', 'l'}

Similarly to :ref:`list comprehensions <tut-listcomps>`, set comprehensions
are also supported::
are also supported, including comprehensions with unpacking::

>>> a = {x for x in 'abracadabra' if x not in 'abc'}
>>> a
{'r', 'd'}

>>> fruits = [{'apple', 'avocado', 'apricot'}, {'banana', 'blueberry'}]
>>> {*fruit for fruit in fruits}
{'blueberry', 'banana', 'avocado', 'apple', 'apricot'}


.. _tut-dictionaries:

Expand Down Expand Up @@ -553,6 +601,18 @@ arbitrary key and value expressions::
>>> {x: x**2 for x in (2, 4, 6)}
{2: 4, 4: 16, 6: 36}

And dictionary unpacking (via ``**``) can be used to merge multiple
dictionaries::

>>> odds = {i: i**2 for i in (1, 3, 5)}
>>> evens = {i: i**2 for i in (2, 4, 6)}
>>> {**odds, **evens}
{1: 1, 3: 9, 5: 25, 2: 4, 4: 16, 6: 36}

>>> all_values = [odds, evens, {0: 0}]
>>> {**i for i in all_values}
{1: 1, 3: 9, 5: 25, 2: 4, 4: 16, 6: 36, 0: 0}

When the keys are simple strings, it is sometimes easier to specify pairs using
keyword arguments::

Expand Down
Loading
Loading