@@ -15,8 +15,7 @@ Abstract
1515========
1616
1717This PEP introduces support for :keyword: `yield from <yield> ` in an
18- :ref: `asynchronous generator function <asynchronous-generator-functions >`
19- through a new ``async yield from `` construct:
18+ :ref: `asynchronous generator function <asynchronous-generator-functions >`:
2019
2120.. code-block :: python
2221
@@ -26,7 +25,7 @@ through a new ``async yield from`` construct:
2625 return 3
2726
2827 async def main ():
29- result = async yield from agenerator()
28+ result = yield from agenerator()
3029 assert result == 3
3130
3231
@@ -39,8 +38,7 @@ This is not to be confused with an :term:`asynchronous generator iterator`,
3938which is the object *returned * by an asynchronous generator.
4039
4140This PEP also uses the term "subgenerator" to refer to a generator, synchronous
42- or asynchronous, that is used inside of a ``yield from `` or ``async yield from ``
43- expression.
41+ or asynchronous, that is used inside a ``yield from ``.
4442
4543
4644Motivation
@@ -104,38 +102,18 @@ item. This comes with a few drawbacks:
104102Specification
105103=============
106104
107-
108- Syntax
109- ------
110-
111-
112105Compiler changes
113- ^^^^^^^^^^^^^^^^
106+ ----------------
114107
115108The compiler will no longer emit a :exc: `SyntaxError ` for
116- :keyword: `return ` statements inside asynchronous generators.
117-
118-
119- Grammar changes
120- ^^^^^^^^^^^^^^^
121-
122- The ``yield_expr `` and ``simple_stmt `` rules need to be updated for the new
123- ``async yield from `` syntax:
124-
125- .. code-block :: peg
126-
127- yield_expr[expr_ty]:
128- | 'async' 'yield' 'from' a=expression
129-
130- simple_stmt[stmt_ty] (memo):
131- | &('yield' | 'async') yield_stmt
109+ :keyword: `return ` or ``yield from `` statements inside asynchronous generators.
132110
133111
134112Changes to ``StopAsyncIteration ``
135113---------------------------------
136114
137115The :class: `StopAsyncIteration ` exception will gain a new ``value `` attribute
138- to be used as the result of ``async yield from `` expressions.
116+ to be used as the result of ``yield from `` expressions in asynchronous generators .
139117
140118This attribute can be supplied by passing a positional argument to
141119``StopAsyncIteration ``. For example:
@@ -156,18 +134,18 @@ If no argument is supplied, ``value`` will be ``None``.
156134In the body of an asynchronous generator function, the statement
157135``return expression `` is roughly equivalent to
158136``raise StopAsyncIteration(expression) ``. However, similar to implicit
159- ``StopIteration `` exceptions raised inside of synchronous generators,
137+ ``StopIteration `` exceptions raised inside synchronous generators,
160138the exception cannot be caught in the body of the asynchronous generator.
161139
162140
163- ``async yield from `` semantics
164- ------------------------------
141+ ``yield from `` semantics in an asynchronous generator
142+ -----------------------------------------------------
165143
166- The statement
144+ In an asynchronous generator, the statement
167145
168146.. code-block :: python
169147
170- RESULT = async yield from EXPR
148+ RESULT = yield from EXPR
171149
172150 is roughly equivalent to the following:
173151
@@ -216,26 +194,24 @@ Rationale
216194=========
217195
218196
219- Relation to `` yield from ``
220- --------------------------
197+ Relation to synchronous generators
198+ ----------------------------------
221199
222- This PEP aims to be very similar to the semantics of ``yield from ``, with the
223- exception that asynchronous generator methods are used instead of synchronous
200+ This PEP aims to be very similar to the semantics of synchronous ``yield from ``,
201+ with the exception that asynchronous generator methods are used instead of synchronous
224202generator methods when delegating. This is a very intuitive design and furthers
225203symmetry with synchronous generators.
226204
227205
228- Choice of ``async yield from `` as the syntax
229- --------------------------------------------
206+ Choice of ``yield from `` as the syntax
207+ --------------------------------------
230208
231- This PEP uses ``async yield from `` as the syntax to ensure that the behavior
232- of the syntax is immediately clear to the user.
233-
234- However, it is acknowledged that this is somewhat verbose. There is not any
235- great solution to this problem; see :ref: `pep-828-rejected-ideas ` for
236- discussion about proposed alternatives. In short, ``async yield from `` was
237- chosen as the best choice of syntax because, while verbose, it is very clear
238- and readable.
209+ This PEP uses ``yield from `` as the syntax, but it is acknowledged that
210+ this is somewhat controversial, as this is an asynchronous context switch
211+ without an explicit ``async `` or ``await `` marker; see
212+ :ref: `pep-828-rejected-ideas `. In short, ``yield from `` was chosen
213+ as the best choice of syntax because it is symmetric with synchronous
214+ generators and easy to type.
239215
240216
241217Backwards Compatibility
@@ -244,7 +220,7 @@ Backwards Compatibility
244220This PEP introduces a backwards-compatible syntax change.
245221
246222The addition of the ``value `` attribute to :exc: `StopAsyncIteration ` is a
247- minor semantic change to an existing builtin exception, but is unlikely
223+ minor semantic change to an existing built-in exception, but is unlikely
248224to affect existing code in practice, as it mirrors the existing ``value ``
249225attribute on :exc: `StopIteration ` and does not affect any other behavior
250226on ``StopAsyncIteration `` or the asynchronous iterator protocol.
@@ -262,10 +238,8 @@ How to Teach This
262238The details of this proposal will be located in Python's canonical
263239documentation, as with all other language constructs. However, this PEP
264240intends to be very intuitive; users should be able to naturally reach
265- for ``async yield from `` given their own background knowledge about generators
266- in Python. This can be encouraged further by suggesting
267- ``async yield from `` in the error message when a user attempts to use
268- ``yield from `` in an asynchronous generator.
241+ for ``yield from `` given their own background knowledge about generators
242+ in Python.
269243
270244
271245Reference Implementation
@@ -281,74 +255,74 @@ Rejected Ideas
281255==============
282256
283257
284- Using ``yield from `` to delegate to asynchronous generators
285- -----------------------------------------------------------
258+ Using ``async yield from `` as the syntax
259+ ----------------------------------------
286260
287- Due to the verbosity of ``async yield from ``, it was proposed to overload
288- the existing ``yield from `` syntax to perform asynchronous subgenerator
289- delegation when used inside of an asynchronous generator.
261+ There are two primary counterarguments against this proposal as it is currently
262+ written:
290263
291- For example:
264+ 1. It adds behavior that does asynchronous work without having ``async `` or
265+ ``await `` as its first non-whitespace token.
266+ 2. It changes the meaning of ``yield from `` depending on the type of generator
267+ it is used in.
292268
293- .. code-block :: python
269+ The solution to these issues was to introduce a new ``async yield from ``
270+ statement that would perform asynchronous subgenerator delegation. However,
271+ this new syntax came with its own set of problems.
294272
295- async def asubgenerator ():
296- yield 1
297- yield 2
273+ First and foremost, ``async yield from `` is very verbose. There are no other
274+ syntax constructs in Python that use three keywords in a row.
298275
299- async def agenerator ():
300- yield from asubgenerator()
276+ Second, there is no ``async yield `` as a counterpart; CPython already emits
277+ significantly different bytecode for ``yield `` statements inside asynchronous
278+ generators, so it's not far-fetched for ``yield from `` to do the same.
301279
280+ Third, ``yield `` (as well as ``yield from ``) is, technically speaking, already
281+ an asynchronous context switch, because the generator will be suspended and can
282+ only be resumed via an ``await `` (on :meth: `~agen.asend `) from the caller.
302283
303- This has the benefit of being more concise than `` async yield from ``, but also
304- has a few downsides.
284+ Finally, plain `` yield from `` is more symmetric and intuitive to users of
285+ Python. To visualize, take these two examples:
305286
306- Most importantly, this makes the asynchronous context switches necessary for
307- delegation implicit, which has no precedent in Python; all syntax that may
308- execute an ``await `` is prefixed with ``async ``. It has been argued that one
309- of the upsides of ``async ``/``await `` over threads is the explicit switch
310- points, so hiding ``await ``\ s behind a ``yield from `` hurts this benefit.
287+ .. code-block :: python
311288
312- Second, many were uncomfortable with ``yield from `` being context-dependent.
313- It felt like a potential footgun for ``yield from `` to mean something
314- different based on the type of generator it was used in. In practice, this
315- may come up in a scenario where one wants to convert a synchronous generator
316- into an asynchronous generator.
289+ def gen ():
290+ yield 1
317291
318- For example, imagine a developer is writing a function for streaming data
319- to the caller:
292+ async def agen ():
293+ yield 1
320294
321295 .. code-block :: python
322296
323- def stream_data ():
324- yield ...
325- yield from something_else()
326- yield ...
297+ def gen ():
298+ yield from subgen()
327299
328- Now, imagine that the developer wants to add an ``await `` call somewhere in
329- this function; the ``yield from something_else() `` statement would suddenly
330- become a runtime :class: `TypeError ` (as opposed to a compile-time
331- :class: `SyntaxError `). With the current proposal, the existence of
332- ``async yield from `` (which would ideally be included in the error message)
333- would make it much clearer that ``something_else `` must also be asynchronous
334- in order to delegate to it.
300+ async def agen ():
301+ yield from asubgen()
302+
303+
304+ ``async yield from `` would break this symmetry.
335305
336- Finally, this would preclude the introduction of support for synchronous
337- subgenerator delegation inside asynchronous generators (see
338- :ref: `pep-828-synchronous-delegation `), because the ``yield from ``
339- syntax would already be overloaded. However, the author of this proposal
340- does acknowledge that the issues with synchronous subdelegation may preclude
341- the introduction of this anyway -- it is not entirely clear whether the issues
342- are solvable given time.
306+ However, all in all, the difference between ``yield from `` and
307+ ``async yield from `` is primarily up to the taste of the reader. Even the
308+ author of this PEP is not completely convinced for one way or the other.
309+ During discussion, the difference between ``yield from `` and ``async yield from ``
310+ was compared to `deciding what 0^0 should be in mathematics
311+ <https://en.wikipedia.org/wiki/Zero_to_the_power_of_zero> `_; it depends on the
312+ context and the point of view. The alternative was to not add support for
313+ delegation to asynchronous subgenerators, which was a lose-lose scenario for
314+ all parties involved. A decision had to be made, and it was clear that consensus
315+ was never going to be reached.
343316
344317
345318``async from ``, ``await from ``, and similar spellings
346- -----------------------------------------------------
319+ *****************************************************
347320
348- As an alternate solution to the verbosity of ``async yield from ``, some have
321+ As an alternative solution to the verbosity of ``async yield from ``, some have
349322suggested using spellings such as ``async from `` in order to cut down on the
350323verbosity. Unfortunately, changes in the spelling will likely hurt the
351- readability of the syntax as a whole.
324+ readability of the syntax as a whole, and thus would not improve the argument
325+ for ``async yield from ``.
352326
353327The benefit of ``async yield from `` is that it specifies each of the three
354328important parts without introducing new keywords. In particular:
@@ -367,10 +341,10 @@ exists.
367341Allowing delegation to synchronous subgenerators
368342------------------------------------------------
369343
370- In an earlier revision of this proposal, the synchronous ``yield from ``
371- construct was allowed in an asynchronous generator, which would delegate to a
372- synchronous generator from an asynchronous one. This had a number of hidden
373- issues.
344+ In an earlier revision of this proposal, ``yield from `` in an asynchronous
345+ generator would delegate to a synchronous generator (and `` async yield from ``
346+ was the counterpart for asynchronous subgenerator delegation).
347+ This had a number of hidden issues.
374348
375349In particular, the mixing of asynchronous frames with synchronous frames had a
376350layer of complexity unfit for Python. In the implementation, there would have
@@ -379,7 +353,7 @@ asynchronous generator methods: :meth:`~agen.asend` to :meth:`~generator.send`,
379353:meth: `~agen.athrow ` to :meth: `~generator.throw `, and :meth: `~agen.aclose `
380354to :meth: `~generator.close `.
381355
382- It's trivial for anyone that needs to delegate to a subgenerator to write
356+ It's trivial for anyone who needs to delegate to a subgenerator to write
383357the wrapper class to upgrade a synchronous :class: `~collections.abc.Iterable `
384358or :class: `~collections.abc.Generator ` to an
385359async one before calling ``async yield from ``.
@@ -427,8 +401,8 @@ async one before calling ``async yield from``.
427401
428402
429403 async def agen ():
430- async yield from AsAsyncIterator([1 , 2 , 3 ])
431- async yield from AsAsyncGenerator(subgen())
404+ yield from AsAsyncIterator([1 , 2 , 3 ])
405+ yield from AsAsyncGenerator(subgen())
432406
433407
434408 To quote Brandt Bucher (paraphrased):
@@ -457,6 +431,11 @@ collecting outside opinions about the design and implementation.
457431Change History
458432==============
459433
434+ - 18-Jul-2026
435+
436+ - Switched from ``async yield from `` to ``yield from `` as the choice of
437+ syntax.
438+
460439- 26-May-2026
461440
462441 - Removed support for delegating to a synchronous subgenerator (via
0 commit comments