






















StopAsyncIterationreturn statements inside asynchronous generatorsasync yield from semanticsyield fromasync yield from as the syntaxyield from to delegate to asynchronous generatorsasync from, await from, and similar spellingsThis PEP introduces support for yield from in an
asynchronous generator function
through a new async yield from construct:
async def agenerator(): yield 1 yield 2 return 3 async def main(): result = async yield from agenerator() assert result == 3
This PEP refers to an async def function that contains a yield
as an asynchronous generator, sometimes suffixed with “function”.
This is not to be confused with an asynchronous generator iterator,
which is the object returned by an asynchronous generator.
This PEP also uses the term “subgenerator” to refer to a generator, synchronous
or asynchronous, that is used inside of a yield from or async yield from
expression.
Historically, yield from was not added to asynchronous generators due to
concerns about the complexity of the implementation. To quote PEP 525:
While it is theoretically possible to implement
yield fromsupport for asynchronous generators, it would require a serious redesign of the generators implementation.
As of March 2026, the author of this proposal does not believe this to be true given the current state of CPython’s asynchronous generator implementation. This proposal comes with a reference implementation to argue this point, but it is acknowledged that complexity is often subjective.
yield from was added to synchronous generators in PEP 380 because
delegation to another generator is a useful thing to do. Due to the
aforementioned complexity in CPython’s generator implementation, PEP 525
omitted support for yield from in asynchronous generators, but this has
left a gap in the language.
This gap has not gone unnoticed by users. There have been three separate
requests for yield from or return behavior (which are closely related)
in asynchronous generators:
Additionally, users have questioned this design decision on Stack Overflow.
The current workaround for the lack of yield from support in asynchronous
generators is to use a for/async for loop that manually yields each
item. This comes with a few drawbacks:
asend(), athrow(), and aclose()
do not interact properly with the caller. This is the primary reason that
yield from was added in the first place.The compiler will no longer emit a SyntaxError for
return statements inside asynchronous generators.
The yield_expr and simple_stmt rules need to be updated for the new
async yield from syntax:
yield_expr[expr_ty]: | 'async' 'yield' 'from' a=expression simple_stmt[stmt_ty] (memo): | &('yield' | 'async') yield_stmt
StopAsyncIterationThe StopAsyncIteration exception will gain a new value attribute
to be used as the result of async yield from expressions.
This attribute can be supplied by passing a positional argument to
StopAsyncIteration. For example:
>>> exception = StopAsyncIteration(42) >>> exception.value 42
If no argument is supplied, value will be None.
return statements inside asynchronous generatorsIn the body of an asynchronous generator function, the statement
return expression is roughly equivalent to
raise StopAsyncIteration(expression). However, similar to implicit
StopIteration exceptions raised inside of synchronous generators,
the exception cannot be caught in the body of the asynchronous generator.
async yield from semanticsThe statement
RESULT = async yield from EXPR
is roughly equivalent to the following:
aiterator = aiter(EXPR) try: item = await anext(aiterator) except StopAsyncIteration as stop: RESULT = stop.value else: while True: try: received = yield item except GeneratorExit as gen_exit: try: aclose = aiterator.aclose except AttributeError: pass else: await aclose() raise gen_exit except BaseException as exception: try: athrow = aiterator.athrow except AttributeError: raise exception from None else: try: item = await athrow(exception) except StopAsyncIteration as stop: RESULT = stop.value break else: try: if received is None: item = await anext(aiterator) else: item = await aiterator.asend(received) except StopAsyncIteration as stop: RESULT = stop.value break
yield fromThis PEP aims to be very similar to the semantics of yield from, with the
exception that asynchronous generator methods are used instead of synchronous
generator methods when delegating. This is a very intuitive design and furthers
symmetry with synchronous generators.
async yield from as the syntaxThis PEP uses async yield from as the syntax to ensure that the behavior
of the syntax is immediately clear to the user.
However, it is acknowledged that this is somewhat verbose. There is not any
great solution to this problem; see Rejected Ideas for
discussion about proposed alternatives. In short, async yield from was
chosen as the best choice of syntax because, while verbose, it is very clear
and readable.
This PEP introduces a backwards-compatible syntax change.
The addition of the value attribute to StopAsyncIteration is a
minor semantic change to an existing builtin exception, but is unlikely
to affect existing code in practice, as it mirrors the existing value
attribute on StopIteration and does not affect any other behavior
on StopAsyncIteration or the asynchronous iterator protocol.
This PEP has no known security implications.
The details of this proposal will be located in Python’s canonical
documentation, as with all other language constructs. However, this PEP
intends to be very intuitive; users should be able to naturally reach
for async yield from given their own background knowledge about generators
in Python. This can be encouraged further by suggesting
async yield from in the error message when a user attempts to use
yield from in an asynchronous generator.
A reference implementation of this PEP can be found at python/cpython#145716.
yield from to delegate to asynchronous generatorsDue to the verbosity of async yield from, it was proposed to overload
the existing yield from syntax to perform asynchronous subgenerator
delegation when used inside of an asynchronous generator.
For example:
async def asubgenerator(): yield 1 yield 2 async def agenerator(): yield from asubgenerator()
This has the benefit of being more concise than async yield from, but also
has a few downsides.
Most importantly, this makes the asynchronous context switches necessary for
delegation implicit, which has no precedent in Python; all syntax that may
execute an await is prefixed with async. It has been argued that one
of the upsides of async/await over threads is the explicit switch
points, so hiding awaits behind a yield from hurts this benefit.
Second, many were uncomfortable with yield from being context-dependent.
It felt like a potential footgun for yield from to mean something
different based on the type of generator it was used in. In practice, this
may come up in a scenario where one wants to convert a synchronous generator
into an asynchronous generator.
For example, imagine a developer is writing a function for streaming data to the caller:
def stream_data(): yield ... yield from something_else() yield ...
Now, imagine that the developer wants to add an await call somewhere in
this function; the yield from something_else() statement would suddenly
become a runtime TypeError (as opposed to a compile-time
SyntaxError). With the current proposal, the existence of
async yield from (which would ideally be included in the error message)
would make it much clearer that something_else must also be asynchronous
in order to delegate to it.
Finally, this would preclude the introduction of support for synchronous
subgenerator delegation inside asynchronous generators (see
Allowing delegation to synchronous subgenerators), because the yield from
syntax would already be overloaded. However, the author of this proposal
does acknowledge that the issues with synchronous subdelegation may preclude
the introduction of this anyway – it is not entirely clear whether the issues
are solvable given time.
async from, await from, and similar spellingsAs an alternate solution to the verbosity of async yield from, some have
suggested using spellings such as async from in order to cut down on the
verbosity. Unfortunately, changes in the spelling will likely hurt the
readability of the syntax as a whole.
The benefit of async yield from is that it specifies each of the three
important parts without introducing new keywords. In particular:
async is necessary to imply an asynchronous context switch.yield is necessary to indicate that the generator will be suspended.from is necessary to differentiate between “standard” generator
suspension (a yield statement) and subgenerator delegation.Given these three constraints, it seems unlikely that a more concise spelling exists.
In an earlier revision of this proposal, the synchronous yield from
construct was allowed in an asynchronous generator, which would delegate to a
synchronous generator from an asynchronous one. This had a number of hidden
issues.
In particular, the mixing of asynchronous frames with synchronous frames had a
layer of complexity unfit for Python. In the implementation, there would have
to be a hidden translation layer between synchronous generator methods and
asynchronous generator methods: asend() to send(),
athrow() to throw(), and aclose()
to close().
For example, asynchronous exceptions could be injected into synchronous generators:
async def agen(): async with asyncio.timeout(3): # If the timeout fails, then an asyncio.TimeoutError would be raised # in a *synchronous* generator! yield from subgen()
To quote Brandt Bucher (paraphrased):
At that point, why not just allow synchronous functions to await coroutines?
In addition, there seemed to be much less demand for this feature compared to support for asynchronous delegation, so solving these issues is less of a priority for now.
Thanks to Bartosz Sławecki for aiding in the development of the reference
implementation of this PEP. In addition, the StopAsyncIteration
changes alongside the support for non-None return values inside
asynchronous generators were largely based on Alex Dixon’s design from
python/cpython#125401.
Special thanks to Yury Selivanov for providing extensive feedback and also collecting outside opinions about the design and implementation.
- Removed support for delegating to a synchronous subgenerator (via a plain
yield from).
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。