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]_. 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 1. Feedback on the initial beta release of Python 3.5 resulted in a
of the object model supporting this PEP to more clearly separate native redesign of the object model supporting this PEP to more clearly
coroutines from generators - rather than being a new kind of generator, separate native coroutines from generators - rather than being a
native coroutines are now their own completely distinct type (implemented new kind of generator, native coroutines are now their own
in [17]_). completely distinct type (implemented in [17]_).
This change was implemented based primarily due to problems encountered This change was implemented based primarily due to problems
attempting to integrate support for native coroutines into the Tornado web encountered attempting to integrate support for native coroutines
server (reported in [18]_). 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 Rationale and Goals
@ -209,11 +227,6 @@ can be one of:
Objects with ``__await__`` method are called *Future-like* objects in Objects with ``__await__`` method are called *Future-like* objects in
the rest of this PEP. 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 It is a ``TypeError`` if ``__await__`` returns anything but an
iterator. iterator.
@ -428,7 +441,7 @@ iteration:
1. An object must implement an ``__aiter__`` method (or, if defined 1. An object must implement an ``__aiter__`` method (or, if defined
with CPython C API, ``tp_as_async.am_aiter`` slot) returning an 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__`` 2. An *asynchronous iterator object* must implement an ``__anext__``
method (or, if defined with CPython C API, ``tp_as_async.am_anext`` method (or, if defined with CPython C API, ``tp_as_async.am_anext``
@ -440,7 +453,7 @@ iteration:
An example of asynchronous iterable:: An example of asynchronous iterable::
class AsyncIterable: class AsyncIterable:
async def __aiter__(self): def __aiter__(self):
return self return self
async def __anext__(self): async def __anext__(self):
@ -468,7 +481,7 @@ proposed::
which is semantically equivalent to:: which is semantically equivalent to::
iter = (ITER) iter = (ITER)
iter = await type(iter).__aiter__(iter) iter = type(iter).__aiter__(iter)
running = True running = True
while running: while running:
try: try:
@ -510,7 +523,7 @@ The following code illustrates new asynchronous iteration protocol::
async def _prefetch(self): async def _prefetch(self):
... ...
async def __aiter__(self): def __aiter__(self):
return self return self
async def __anext__(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:: which would be equivalent to the following code::
i = await Cursor().__aiter__() i = Cursor().__aiter__()
while True: while True:
try: try:
row = await i.__anext__() row = await i.__anext__()
@ -551,7 +564,7 @@ iterators.
def __init__(self, obj): def __init__(self, obj):
self._it = iter(obj) self._it = iter(obj)
async def __aiter__(self): def __aiter__(self):
return self return self
async def __anext__(self): async def __anext__(self):
@ -814,37 +827,6 @@ Asynchronous iterator
`Asynchronous Iterators and "async for"`_ for details. `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 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). 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 PEP 492 was accepted in CPython 3.5.0 with ``__aiter__`` defined as
several good reasons to make it a coroutine: a method, that was expected to return an awaitable resolving to an
asynchronous iterator.
* as most of the ``__anext__``, ``__aenter__``, and ``__aexit__`` In 3.5.2 (as PEP 492 was accepted on a provisional basis) the
methods are coroutines, users would often make a mistake defining it ``__aiter__`` protocol was updated to return asynchronous iterators
as ``async`` anyways; directly.
* there might be a need to run some asynchronous operations in The motivation behind this change is to make it possible to
``__aiter__``, for instance to prepare DB queries or do some file implement asynchronous generators in Python. See [19]_ for
operation. more details.
Importance of "async" keyword Importance of "async" keyword
@ -1171,8 +1154,8 @@ Why magic methods start with "a"
New asynchronous magic methods ``__aiter__``, ``__anext__``, New asynchronous magic methods ``__aiter__``, ``__anext__``,
``__aenter__``, and ``__aexit__`` all start with the same prefix "a". ``__aenter__``, and ``__aexit__`` all start with the same prefix "a".
An alternative proposal is to use "async" prefix, so that ``__aiter__`` An alternative proposal is to use "async" prefix, so that ``__anext__``
becomes ``__async_iter__``. However, to align new magic methods with becomes ``__async_next__``. However, to align new magic methods with
the existing ones, such as ``__radd__`` and ``__iadd__`` it was decided the existing ones, such as ``__radd__`` and ``__iadd__`` it was decided
to use a shorter version. to use a shorter version.
@ -1454,6 +1437,8 @@ References
.. [18] http://bugs.python.org/issue24400 .. [18] http://bugs.python.org/issue24400
.. [19] http://bugs.python.org/issue27243
Acknowledgments Acknowledgments
=============== ===============