PEP 492: Update __aiter__ protocol

This commit is contained in:
Yury Selivanov 2016-06-09 17:57:41 -04:00
parent b264a3e53e
commit e6f60d8dce
1 changed files with 49 additions and 64 deletions

View File

@ -40,18 +40,36 @@ programming, as many other languages have adopted, or are planning to
adopt, similar features: [2]_, [5]_, [6]_, [7]_, [8]_, [10]_.
API Design and Implementation Note
==================================
API Design and Implementation Revisions
=======================================
Feedback on the initial beta release of Python 3.5 resulted in a redesign
of the object model supporting this PEP to more clearly separate native
coroutines from generators - rather than being a new kind of generator,
native coroutines are now their own completely distinct type (implemented
in [17]_).
1. Feedback on the initial beta release of Python 3.5 resulted in a
redesign of the object model supporting this PEP to more clearly
separate native coroutines from generators - rather than being a
new kind of generator, native coroutines are now their own
completely distinct type (implemented in [17]_).
This change was implemented based primarily due to problems encountered
attempting to integrate support for native coroutines into the Tornado web
server (reported in [18]_).
This change was implemented based primarily due to problems
encountered attempting to integrate support for native coroutines
into the Tornado web server (reported in [18]_).
2. In CPython 3.5.2, the ``__aiter__`` protocol was updated.
Before 3.5.2, ``__aiter__`` was expected to return an *awaitable*
resolving to an *asynchronous iterator*. Starting with 3.5.2,
``__aiter__`` should return asynchronous iterators directly.
If the old protocol is used in 3.5.2, Python will raise a
``PendingDeprecationWarning``.
In CPython 3.6, the old ``__aiter__`` protocol will still be
supported with a ``DeprecationWarning`` being raised.
In CPython 3.7, the old ``__aiter__`` protocol will no longer be
supported: a ``RuntimeError`` will be raised if ``__aiter__``
returns anything but an asynchronous iterator.
See [19]_ for more details.
Rationale and Goals
@ -209,11 +227,6 @@ can be one of:
Objects with ``__await__`` method are called *Future-like* objects in
the rest of this PEP.
Also, please note that ``__aiter__`` method (see its definition
below) cannot be used for this purpose. It is a different protocol,
and would be like using ``__iter__`` instead of ``__call__`` for
regular callables.
It is a ``TypeError`` if ``__await__`` returns anything but an
iterator.
@ -428,7 +441,7 @@ iteration:
1. An object must implement an ``__aiter__`` method (or, if defined
with CPython C API, ``tp_as_async.am_aiter`` slot) returning an
*awaitable* resulting in an *asynchronous iterator object*.
*asynchronous iterator object*.
2. An *asynchronous iterator object* must implement an ``__anext__``
method (or, if defined with CPython C API, ``tp_as_async.am_anext``
@ -440,7 +453,7 @@ iteration:
An example of asynchronous iterable::
class AsyncIterable:
async def __aiter__(self):
def __aiter__(self):
return self
async def __anext__(self):
@ -468,7 +481,7 @@ proposed::
which is semantically equivalent to::
iter = (ITER)
iter = await type(iter).__aiter__(iter)
iter = type(iter).__aiter__(iter)
running = True
while running:
try:
@ -510,7 +523,7 @@ The following code illustrates new asynchronous iteration protocol::
async def _prefetch(self):
...
async def __aiter__(self):
def __aiter__(self):
return self
async def __anext__(self):
@ -527,7 +540,7 @@ then the ``Cursor`` class can be used as follows::
which would be equivalent to the following code::
i = await Cursor().__aiter__()
i = Cursor().__aiter__()
while True:
try:
row = await i.__anext__()
@ -551,7 +564,7 @@ iterators.
def __init__(self, obj):
self._it = iter(obj)
async def __aiter__(self):
def __aiter__(self):
return self
async def __anext__(self):
@ -814,37 +827,6 @@ Asynchronous iterator
`Asynchronous Iterators and "async for"`_ for details.
List of functions and methods
=============================
================= =================================== =================
Method Can contain Can't contain
================= =================================== =================
async def func await, return value yield, yield from
async def __a*__ await, return value yield, yield from
def __a*__ return awaitable await
def __await__ yield, yield from, return iterable await
generator yield, yield from, return value await
================= =================================== =================
Where:
* "async def func": native coroutine;
* "async def __a*__": ``__aiter__``, ``__anext__``, ``__aenter__``,
``__aexit__`` defined with the ``async`` keyword;
* "def __a*__": ``__aiter__``, ``__anext__``, ``__aenter__``,
``__aexit__`` defined without the ``async`` keyword, must return an
*awaitable*;
* "def __await__": ``__await__`` method to implement *Future-like*
objects;
* generator: a "regular" generator, function defined with ``def`` and
which contains a least one ``yield`` or ``yield from`` expression.
Transition Plan
===============
@ -1080,19 +1062,20 @@ async/await, and because it makes working with many languages in one
project easier (Python with ECMAScript 7 for instance).
Why "__aiter__" returns awaitable
---------------------------------
Why "__aiter__" does not return an awaitable
--------------------------------------------
In principle, ``__aiter__`` could be a regular function. There are
several good reasons to make it a coroutine:
PEP 492 was accepted in CPython 3.5.0 with ``__aiter__`` defined as
a method, that was expected to return an awaitable resolving to an
asynchronous iterator.
* as most of the ``__anext__``, ``__aenter__``, and ``__aexit__``
methods are coroutines, users would often make a mistake defining it
as ``async`` anyways;
In 3.5.2 (as PEP 492 was accepted on a provisional basis) the
``__aiter__`` protocol was updated to return asynchronous iterators
directly.
* there might be a need to run some asynchronous operations in
``__aiter__``, for instance to prepare DB queries or do some file
operation.
The motivation behind this change is to make it possible to
implement asynchronous generators in Python. See [19]_ for
more details.
Importance of "async" keyword
@ -1171,8 +1154,8 @@ Why magic methods start with "a"
New asynchronous magic methods ``__aiter__``, ``__anext__``,
``__aenter__``, and ``__aexit__`` all start with the same prefix "a".
An alternative proposal is to use "async" prefix, so that ``__aiter__``
becomes ``__async_iter__``. However, to align new magic methods with
An alternative proposal is to use "async" prefix, so that ``__anext__``
becomes ``__async_next__``. However, to align new magic methods with
the existing ones, such as ``__radd__`` and ``__iadd__`` it was decided
to use a shorter version.
@ -1454,6 +1437,8 @@ References
.. [18] http://bugs.python.org/issue24400
.. [19] http://bugs.python.org/issue27243
Acknowledgments
===============