@@ -629,6 +629,7 @@ Yield expressions
629629 yield_atom: "(" `yield_expression ` ")"
630630 yield_from: "yield" "from" `expression `
631631 yield_expression: "yield" `yield_list ` | `yield_from `
632+ async_yield_from: "async" "yield" "from" `expression `
632633
633634The yield expression is used when defining a :term: `generator ` function
634635or an :term: `asynchronous generator ` function and
@@ -708,6 +709,10 @@ the yield expression. It can be either set explicitly when raising
708709.. versionchanged :: 3.3
709710 Added ``yield from <expr> `` to delegate control flow to a subiterator.
710711
712+ .. versionchanged :: next
713+ ``yield from `` is now allowed to be used in an async generator.
714+ Previously, it would raise a :class: `SyntaxError `.
715+
711716The parentheses may be omitted when the yield expression is the sole expression
712717on the right hand side of an assignment statement.
713718
@@ -728,6 +733,10 @@ on the right hand side of an assignment statement.
728733 The proposal that expanded on :pep: `492 ` by adding generator capabilities to
729734 coroutine functions.
730735
736+ :pep: `828 ` - Supporting ``yield from `` in asynchronous generators
737+ The proposal that expanded on :pep: `380 ` by adding subgenerator delegation
738+ to asynchronous generators.
739+
731740.. index :: pair: object; generator
732741.. _generator-methods :
733742
@@ -913,7 +922,89 @@ registered *finalizer* to be called upon finalization. For a reference example
913922of a *finalizer * method see the implementation of
914923``asyncio.Loop.shutdown_asyncgens `` in :source: `Lib/asyncio/base_events.py `.
915924
916- The expression ``yield from <expr> `` is a syntax error when used in an
925+ .. _async-yield-from :
926+
927+ Asynchronous ``yield from ``
928+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
929+
930+ Conceptually, ``async yield from `` is very similar to ``yield from ``, but
931+ operates solely on asynchronous constructs rather than synchronous ones.
932+ In particular:
933+
934+ .. list-table ::
935+ :widths: auto
936+ :header-rows: 1
937+
938+ * * ``yield from `` construct
939+ * ``async yield from `` construct
940+ * * :meth: `~object.__iter__ `
941+ * :meth: `~object.__aiter__ `
942+ * * :meth: `~generator.__next__ `
943+ * :meth: `~agen.__anext__ `
944+ * * :meth: `~generator.send `
945+ * :meth: `~agen.asend `
946+ * * :class: `StopIteration `
947+ * :class: `StopAsyncIteration `
948+
949+ To describe the above:
950+
951+ * The object being delegated to must be asynchronously iterable (that is, it
952+ must implement ``__aiter__ `` instead of ``__iter__ ``).
953+ * When ``anext `` is called on the parent generator (the one that contains
954+ ``async yield from ``), ``__anext__ `` will be invoked on the subgenerator.
955+ In contrast, a synchronous ``yield from `` would invoke ``__next__ `` instead.
956+ (Note that calling ``asend `` with a ``None `` value is equivalent to calling
957+ ``anext() ``, and thus applies here.)
958+ * All calls to ``asend ``, ``athrow ``, and ``aclose `` are delegated to the
959+ subgenerator (the object returned by ``__aiter__ `` in this case). This means
960+ that a call to ``parent_generator.asend(x) `` is semantically equivalent to
961+ ``sub_generator.asend(x) ``, where ``parent_generator `` is currently executing
962+ an ``async yield from `` on ``sub_generator ``.
963+ * The result of the expression is retrieved through
964+ :attr: `StopAsyncIteration.value ` instead of :attr: `StopIteration.value `.
965+
966+ An example of usage for ``async yield from ``:
967+
968+ .. code-block :: pycon
969+
970+ >>> import asyncio
971+ >>> async def sleepy_count(number):
972+ ... for num in range(number):
973+ ... await asyncio.sleep(1)
974+ ... result = yield num
975+ ... print(f"Got result: {result}")
976+ ...
977+ >>> async def counter():
978+ ... final_number = async yield from sleepy_count(5)
979+ ... yield final_number
980+ ...
981+ >>> await anext(ag)
982+ 0
983+ >>> await anext(ag)
984+ Got result: None
985+ 1
986+ >>> await ag.asend(42)
987+ Got result: 42
988+ 2
989+ >>> await ag.athrow(ValueError("Nobody expects the Spanish Inquisition"))
990+ Traceback (most recent call last):
991+ File "/home/python/cpython/Lib/concurrent/futures/_base.py", line 450, in result
992+ return self.__get_result()
993+ ~~~~~~~~~~~~~~~~~^^
994+ File "/home/python/cpython/Lib/concurrent/futures/_base.py", line 395, in __get_result
995+ raise self._exception
996+ File "<python-input-4>", line 1, in <module>
997+ await ag.athrow(ValueError("Nobody expects the Spanish Inquisition"))
998+ File "<python-input-0>", line 8, in counter
999+ final_number = async yield from sleepy_count(4)
1000+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1001+ File "<python-input-0>", line 3, in sleepy_count
1002+ result = yield num
1003+ ^^^^^^^^^
1004+ ValueError: Nobody expects the Spanish Inquisition
1005+
1006+
1007+ ``async yield from `` is a :class: `SyntaxError ` when used outside of an
9171008asynchronous generator function.
9181009
9191010.. index :: pair: object; asynchronous-generator
0 commit comments