Skip to content
Open
Show file tree
Hide file tree
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
11 changes: 11 additions & 0 deletions Doc/library/exceptions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -526,8 +526,19 @@ The following exceptions are the exceptions that are usually raised.
Must be raised by :meth:`~object.__anext__` method of an
:term:`asynchronous iterator` object to stop the iteration.

.. attribute:: StopAsyncIteration.value

This is given as an argument when constructing the exception, and
defaults to :const:`None`. This is used for the result of
``yield from`` expressions (see :ref:`async-yield-from`).

.. versionadded:: next

.. versionadded:: 3.5

.. versionchanged:: next
Added the ``value`` attribute.

.. exception:: SyntaxError(message, details)

Raised when the parser encounters a syntax error. This may occur in an
Expand Down
90 changes: 90 additions & 0 deletions Doc/reference/expressions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1159,6 +1159,10 @@ the yield expression. It can be either set explicitly when raising
.. versionchanged:: 3.3
Added ``yield from <expr>`` to delegate control flow to a subiterator.

.. versionchanged:: next
``yield from`` is now allowed to be used in an async generator.
Previously, it would raise a :class:`SyntaxError`.

The parentheses may be omitted when the yield expression is the sole expression
on the right hand side of an assignment statement.

Expand All @@ -1179,6 +1183,10 @@ on the right hand side of an assignment statement.
The proposal that expanded on :pep:`492` by adding generator capabilities to
coroutine functions.

:pep:`828` - Supporting ``yield from`` in asynchronous generators
The proposal that expanded on :pep:`380` by adding subgenerator delegation
to asynchronous generators.

.. index:: pair: object; generator
.. _generator-methods:

Expand Down Expand Up @@ -1367,6 +1375,88 @@ of a *finalizer* method see the implementation of
The expression ``yield from <expr>`` is a syntax error when used in an
asynchronous generator function.

.. _async-yield-from:

Asynchronous ``yield from``
^^^^^^^^^^^^^^^^^^^^^^^^^^^

In async generators, the ``yield from`` statement operates solely on
asynchronous constructs rather than synchronous ones.
In particular:

.. list-table::
:widths: auto
:header-rows: 1

* * Synchronous ``yield from``
* Asynchronous ``yield from``
* * :meth:`~object.__iter__`
* :meth:`~object.__aiter__`
* * :meth:`~generator.__next__`
* :meth:`~agen.__anext__`
* * :meth:`~generator.send`
* :meth:`~agen.asend`
* * :class:`StopIteration`
* :class:`StopAsyncIteration`

To describe the above:

* The object being delegated to must be asynchronously iterable (that is, it
must implement ``__aiter__`` instead of ``__iter__``).
* When ``anext`` is called on the parent generator (the one that contains
``yield from``), ``__anext__`` will be invoked on the subgenerator.
In contrast, a synchronous ``yield from`` would invoke ``__next__`` instead.
(Note that calling ``asend`` with a ``None`` value is equivalent to calling
``anext()``, and thus applies here.)
* All calls to ``asend``, ``athrow``, and ``aclose`` are delegated to the
subgenerator (the object returned by ``__aiter__`` in this case). This means
that a call to ``parent_generator.asend(x)`` is semantically equivalent to
``sub_generator.asend(x)``, where ``parent_generator`` is currently executing
an asynchronous ``yield from`` on ``sub_generator``.
* The result of the expression is retrieved through
:attr:`StopAsyncIteration.value` instead of :attr:`StopIteration.value`.

An example of usage for ``yield from`` in async generator:

.. code-block:: pycon

>>> import asyncio
>>> async def sleepy_count(number):
... for num in range(number):
... await asyncio.sleep(1)
... result = yield num
... print(f"Got result: {result}")
...
>>> async def counter():
... final_number = yield from sleepy_count(5)
... yield final_number
...
>>> await anext(ag)
0
>>> await anext(ag)
Got result: None
1
>>> await ag.asend(42)
Got result: 42
2
>>> await ag.athrow(ValueError("Nobody expects the Spanish Inquisition"))
Traceback (most recent call last):
File "/home/python/cpython/Lib/concurrent/futures/_base.py", line 450, in result
return self.__get_result()
~~~~~~~~~~~~~~~~~^^
File "/home/python/cpython/Lib/concurrent/futures/_base.py", line 395, in __get_result
raise self._exception
File "<python-input-4>", line 1, in <module>
await ag.athrow(ValueError("Nobody expects the Spanish Inquisition"))
File "<python-input-0>", line 8, in counter
final_number = yield from sleepy_count(4)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<python-input-0>", line 3, in sleepy_count
result = yield num
^^^^^^^^^
ValueError: Nobody expects the Spanish Inquisition


.. index:: pair: object; asynchronous-generator
.. _asynchronous-generator-methods:

Expand Down
27 changes: 26 additions & 1 deletion Doc/whatsnew/3.16.rst
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,38 @@ Summary --- release highlights
Brevity is key.


.. PEP-sized items next.
* :pep:`828`: :ref:`'yield from' in async generators <whatsnew315-async-yield-from>`



New features
============

.. _whatsnew315-async-yield-from:

:pep:`828`: Supporting ``yield from`` in asynchronous generators
----------------------------------------------------------------

Use of the :keyword:`yield from <yield>` construct and the :keyword:`return`
statement with a non-``None`` value is now allowed in an
:term:`asynchronous generator function <asynchronous generator>`. For example,
the following code would previously raise a :class:`SyntaxError`:

.. code-block:: python

async def asubgen():
yield 2
yield 3
yield 4

async def agenerator():
yield 1
yield from asubgen() # Now allowed!
return 5 # Now allowed!

.. seealso:: :pep:`828` for further details.

(Contributed by Peter Bierma in :gh:`155126`.)


Other language changes
Expand Down
5 changes: 5 additions & 0 deletions Include/cpython/pyerrors.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ typedef struct {
PyObject *value;
} PyStopIterationObject;

typedef struct {
PyException_HEAD
PyObject *value;
} PyStopAsyncIterationObject;

typedef struct {
PyException_HEAD
PyObject *name;
Expand Down
4 changes: 2 additions & 2 deletions Include/internal/pycore_magic_number.h
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ Known values:
Python 3.16a1 3702 (Replace DELETE_NAME with PUSH_NULL; STORE_NAME)
Python 3.16a1 3703 (Replace DELETE_GLOBAL with PUSH_NULL; STORE_GLOBAL)
Python 3.16a1 3704 (Replace DELETE_ATTR with PUSH_NULL; STORE_ATTR)
Python 3.16a1 3705 (PEP 828: yield from for asyncgens)
Python 3.17 will start with 3750
Expand All @@ -312,7 +312,7 @@ Known values:
*/

#define PYC_MAGIC_NUMBER 3704
#define PYC_MAGIC_NUMBER 3705
/* This is equivalent to converting PYC_MAGIC_NUMBER to 2 bytes
(little-endian) and then appending b'\r\n'. */
#define PYC_MAGIC_NUMBER_TOKEN \
Expand Down
9 changes: 7 additions & 2 deletions Include/internal/pycore_opcode_metadata.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Include/internal/pycore_uop_ids.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading