Skip to content
Open
Changes from all commits
Commits
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
91 changes: 41 additions & 50 deletions peps/pep-0798.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ Title: Unpacking in Comprehensions
Author: Adam Hartz <[email protected]>, Erik Demaine <[email protected]>
Sponsor: Jelle Zijlstra <jelle.zijlstra at gmail.com>
Discussions-To: https://discuss.python.org/t/99435
Status: Draft
Status: Accepted
Type: Standards Track
Created: 19-Jul-2025
Python-Version: 3.15
Post-History: `16-Oct-2021 <https://mail.python.org/archives/list/[email protected]/thread/7G732VMDWCRMWM4PKRG6ZMUKH7SUC7SH/>`__, `22-Jun-2025 <https://discuss.python.org/t/pre-pep-unpacking-in-comprehensions/96362>`__, `19-Jul-2025 <https://discuss.python.org/t/pep-798-unpacking-in-comprehensions/99435>`__
Resolution: `03-Nov-2025 <https://discuss.python.org/t/pep-798-unpacking-in-comprehensions/99435/60>`__


Abstract
Expand Down Expand Up @@ -109,7 +110,7 @@ number of iterables.

This proposal represents a natural extension of the language, paralleling
existing syntactic structures: where ``[x, y, z]`` creates a list from a fixed
number of vaues, ``[item for item in items]`` creates a list from an arbitrary
number of values, ``[item for item in items]`` creates a list from an arbitrary
number of values; this proposal extends that notion to the construction of
lists that involve unpacking, making ``[*item for item in items]`` analogous to
``[*x, *y, *z]``.
Expand Down Expand Up @@ -222,19 +223,17 @@ Semantics: Generator Expressions
Generator expressions using the unpacking syntax should form new generators
producing values from the concatenation of the iterables given by the
expressions. Specifically, the behavior is defined to be equivalent to the
following::
following (though without defining or referencing the looping variable ``i``)::

# equivalent to g = (*expr for x in it)
def generator():
for x in it:
yield from expr
for i in expr:
yield i

g = generator()

Since ``yield from`` is not allowed inside of async generators (see the section
of :pep:`525` on Asynchronous ``yield from``), the equivalent for ``(*expr
async for x in ait())`` is more like the following (though of course this new
form should not define or reference the looping variable ``i``)::
.. code:: python

# equivalent to g = (*expr async for x in ait())
async def generator():
Expand All @@ -244,11 +243,8 @@ form should not define or reference the looping variable ``i``)::

g = generator()

The specifics of these semantics should be revisited in the future,
particularly if async generators receive support for ``yield from`` (in which
case the async variant may wish to be changed to make use of ``yield from``
instead of an explicit loop). See :ref:`pep798-alternativegenexpsemantics` for
more discussion.

See :ref:`pep798-alternativegenexpsemantics` for more discussion of these semantics.

Interaction with Assignment Expressions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -267,7 +263,8 @@ form, ``y`` will be bound in the containing scope instead of locally::

def generator():
for i in (0, 2, 4):
yield from (y := [i, i+1])
for j in (y := [i, i+1]):
yield j

In this example, the subexpression ``(y := [i, i+1])`` is evaluated exactly
three times before the generator is exhausted: just after assigning ``i`` in
Expand Down Expand Up @@ -402,13 +399,14 @@ particular phrasing of any of the old error messages being replaced, which we
expect to be rare.

One related concern is that a hypothetical future decision to change the
semantics of async generator expressions to make use of ``yield from`` during
semantics of generator expressions to make use of ``yield from`` during
unpacking (delegating to generators that are being unpacked) would not be
backwards-compatible because it would affect the behavior of the resulting
generators when used with ``.asend()``, ``.athrow()``, and ``.aclose()``. That
said, despite being backwards-incompatible, such a change would be unlikely to
have a large impact because it would only affect the behavior of structures
that, under this proposal, are not particularly useful. See
generators when used with ``.send()``/``.asend()``, ``.throw()``/``.athrow()``,
and ``.close()``/``.aclose()``. That said, despite being
backwards-incompatible, such a change would be unlikely to have a large impact
because it would only affect the behavior of structures that, under this
proposal, are not particularly useful. See
:ref:`pep798-alternativegenexpsemantics` for more discussion.

.. _pep798-examples:
Expand Down Expand Up @@ -759,59 +757,52 @@ a `poll in the Discourse thread
<https://discuss.python.org/t/pep-798-unpacking-in-comprehensions/99435/33>`__.
Beyond the proposal outlined above, the following were also considered:

1. Using explicit loops for both synchronous and asynchronous generator
expressions.
1. Using ``yield from`` for unpacking in synchronous generator expressions but
using an explicit loop in asynchronous generator expressions (as proposed in
the original draft of this PEP).

This strategy would have resulted in a symmetry between synchronous and
asynchronous generator expressions but would have prevented a
potentially-useful tool by disallowing delegation in the case of synchronous
generator expressions. One specific concern with this approach is the
introduction of an asymmetry between synchronous and asynchronous
generators, but this concern is mitigated by the fact that these asymmetries
already exist between synchronous and asynchronous generators more
generally.
This strategy would have allowed unpacking in generator expressions to
closely mimic a popular way of writing generators that perform this
operation (using ``yield from``), but it would also have created an
asymmetry between synchronous and asynchronous versions, and also between
this new syntax and ``itertools.chain`` and the double-loop version.

2. Using ``yield from`` for unpacking in synchronous generator expressions and
mimicking the behavior of ``yield from`` for unpacking in async generator
expressions.

This strategy would also make unpacking in synchronous and asynchronous
generators behave symmetrically, but it would also be more complex, enough
so that the cost may not be worth the benefit. As such, this PEP proposes
that asynchronous generator expressions using the unpacking operator should
not adopt semantics similar to ``yield from`` until ``yield from`` is
supported in asynchronous generators more generally.
so that the cost may not be worth the benefit, particularly in the absence
of a compelling use case for delegation.

3. Using ``yield from`` for unpacking in synchronous generator expressions, and
disallowing unpacking in asynchronous generator expressions until they
support ``yield from``.

This strategy could possibly reduce friction if asynchronous generator
expressions do gain support for ``yield from`` in the future by making sure
that any decision made at that point would be fully backwards-compatible.
But the utility of unpacking in that context seems to outweigh the potential
downside of a minimally-invasive backwards-incompatible change in the future
if async generator expressions do receive support for ``yield from``.
that any decision made at that point would be fully backwards-compatible,
but in the meantime, it would result in an even bigger discrepancy between
synchronous and asynchronous generator expressions than option 1.

4. Disallowing unpacking in all generator expressions.

This would retain symmetry between the two cases, but with the downside of
losing a very expressive form.

losing an expressive form and reducing symmetry between list/set
comprehensions and generator expressions.

Each of these options (including the one presented in this PEP) has its
benefits and drawbacks, with no option being clearly superior on all fronts.
The semantics proposed in :ref:`pep798-genexpsemantics` represent a reasonable
compromise where unpacking in both synchronous and asynchronous generator
expressions mirrors common ways of writing equivalent generators currently.
Moreover, these subtle differences are unlikely to be impactful for common use
cases (for example, there is no difference for the likely most-common use case
of combining simple collections).

As suggested above, this decision should be revisited in the event that
asynchronous generators receive support for ``yield from`` in the future, in
which case adjusting the semantics of unpacking in async generator expressions
to use ``yield from`` should be considered.
The semantics proposed in :ref:`pep798-genexpsemantics` above represent a
reasonable compromise by allowing exactly the same kind of unpacking in
synchronous and asynchronous generator expressions and retaining an existing
property of generator expressions (that they do not delegate to subgenerators).

This decision should be revisited in the event that asynchronous generators
receive support for ``yield from`` in the future, in which case adjusting the
semantics of unpacking in generator expressions to use ``yield from`` should be
considered.


Concerns and Disadvantages
Expand Down