pep-0492: v4.

This commit is contained in:
Yury Selivanov 2015-04-29 21:11:53 -04:00
parent ef2437d422
commit c28890fb42
1 changed files with 201 additions and 147 deletions

View File

@ -8,7 +8,7 @@ Type: Standards Track
Content-Type: text/x-rst Content-Type: text/x-rst
Created: 09-Apr-2015 Created: 09-Apr-2015
Python-Version: 3.5 Python-Version: 3.5
Post-History: 17-Apr-2015, 21-Apr-2015, 27-Apr-2015 Post-History: 17-Apr-2015, 21-Apr-2015, 27-Apr-2015, 29-Apr-2015
Abstract Abstract
@ -57,8 +57,7 @@ Specification
============= =============
This proposal introduces new syntax and semantics to enhance coroutine This proposal introduces new syntax and semantics to enhance coroutine
support in Python, it does not change the internal implementation of support in Python.
coroutines, which are still based on generators.
This specification presumes knowledge of the implementation of This specification presumes knowledge of the implementation of
coroutines in Python (PEP 342 and PEP 380). Motivation for the syntax coroutines in Python (PEP 342 and PEP 380). Motivation for the syntax
@ -66,21 +65,22 @@ changes proposed here comes from the asyncio framework (PEP 3156) and
the "Cofunctions" proposal (PEP 3152, now rejected in favor of this the "Cofunctions" proposal (PEP 3152, now rejected in favor of this
specification). specification).
From this point in this document we use the word *coroutine* to refer From this point in this document we use the word *native coroutine* to
to functions declared using the new syntax. *generator-based refer to functions declared using the new syntax. *generator-based
coroutine* is used where necessary to refer to coroutines that are coroutine* is used where necessary to refer to coroutines that are
based on generator syntax. based on generator syntax. *coroutine* is used in contexts where both
definitions are applicable.
New Coroutine Declaration Syntax New Coroutine Declaration Syntax
-------------------------------- --------------------------------
The following new syntax is used to declare a coroutine:: The following new syntax is used to declare a *native coroutine*::
async def read_data(db): async def read_data(db):
pass pass
Key properties of coroutines: Key properties of *native coroutines*:
* ``async def`` functions are always coroutines, even if they do not * ``async def`` functions are always coroutines, even if they do not
contain ``await`` expressions. contain ``await`` expressions.
@ -88,10 +88,16 @@ Key properties of coroutines:
* It is a ``SyntaxError`` to have ``yield`` or ``yield from`` * It is a ``SyntaxError`` to have ``yield`` or ``yield from``
expressions in an ``async`` function. expressions in an ``async`` function.
* Internally, a new code object flag - ``CO_COROUTINE`` - is introduced * Internally, two new code object flags were introduced:
to enable runtime detection of coroutines (and migrating existing
code). All coroutines have both ``CO_COROUTINE`` and ``CO_GENERATOR`` - ``CO_COROUTINE`` is used to enable runtime detection of
flags set. *coroutines* (and migrating existing code).
- ``CO_NATIVE_COROUTINE`` is used to mark *native coroutines*
(defined with new syntax.)
All coroutines have ``CO_COROUTINE``, ``CO_NATIVE_COROUTINE``, and
``CO_GENERATOR`` flags set.
* Regular generators, when called, return a *generator object*; * Regular generators, when called, return a *generator object*;
similarly, coroutines return a *coroutine object*. similarly, coroutines return a *coroutine object*.
@ -100,15 +106,25 @@ Key properties of coroutines:
and are replaced with a ``RuntimeError``. For regular generators and are replaced with a ``RuntimeError``. For regular generators
such behavior requires a future import (see PEP 479). such behavior requires a future import (see PEP 479).
* See also `Coroutine objects`_ section.
types.coroutine() types.coroutine()
----------------- -----------------
A new function ``coroutine(gen)`` is added to the ``types`` module. It A new function ``coroutine(gen)`` is added to the ``types`` module. It
applies ``CO_COROUTINE`` flag to the passed generator-function's code allows interoperability between existing *generator-based coroutines*
object, making it to return a *coroutine object* when called. in asyncio and *native coroutines* introduced by this PEP.
This feature enables an easy upgrade path for existing libraries. The function applies ``CO_COROUTINE`` flag to generator-function's code
object, making it return a *coroutine object*.
The function can be used as a decorator, since it modifies generator-
functions in-place and returns them.
Note, that the ``CO_NATIVE_COROUTINE`` flag is not applied by
``types.coroutine()`` to make it possible to separate *native
coroutines* defined with new syntax, from *generator-based coroutines*.
Await Expression Await Expression
@ -129,7 +145,9 @@ It uses the ``yield from`` implementation with an extra step of
validating its argument. ``await`` only accepts an *awaitable*, which validating its argument. ``await`` only accepts an *awaitable*, which
can be one of: can be one of:
* A *coroutine object* returned from a *coroutine* or a generator * A *native coroutine object* returned from a *native coroutine*.
* A *generator-based coroutine object* returned from a generator
decorated with ``types.coroutine()``. decorated with ``types.coroutine()``.
* An object with an ``__await__`` method returning an iterator. * An object with an ``__await__`` method returning an iterator.
@ -142,7 +160,7 @@ can be one of:
explanation.) explanation.)
To enable this behavior for coroutines, a new magic method called To enable this behavior for coroutines, a new magic method called
``__await__`` is added. In asyncio, for instance, to enable Future ``__await__`` is added. In asyncio, for instance, to enable *Future*
objects in ``await`` statements, the only change is to add objects in ``await`` statements, the only change is to add
``__await__ = __iter__`` line to ``asyncio.Future`` class. ``__await__ = __iter__`` line to ``asyncio.Future`` class.
@ -160,7 +178,9 @@ can be one of:
* Objects defined with CPython C API with a ``tp_await`` function, * Objects defined with CPython C API with a ``tp_await`` function,
returning an iterator (similar to ``__await__`` method). returning an iterator (similar to ``__await__`` method).
It is a ``SyntaxError`` to use ``await`` outside of a coroutine. It is a ``SyntaxError`` to use ``await`` outside of an ``async def``
function (like it is a ``SyntaxError`` to use ``yield`` outside of
``def`` function.)
It is a ``TypeError`` to pass anything other than an *awaitable* object It is a ``TypeError`` to pass anything other than an *awaitable* object
to an ``await`` expression. to an ``await`` expression.
@ -169,11 +189,22 @@ to an ``await`` expression.
Updated operator precedence table Updated operator precedence table
''''''''''''''''''''''''''''''''' '''''''''''''''''''''''''''''''''
``await`` keyword is defined differently from ``yield`` and ``yield ``await`` keyword is defined as follows::
from`` in the Grammar.
The key difference is that *await expressions* do not require power ::= await ["**" u_expr]
parentheses around them most of the times. await ::= ["await"] primary
where "primary" represents the most tightly bound operations of the
language. Its syntax is::
primary ::= atom | attributeref | subscription | slicing | call
See Python Documentation [12]_ and `Grammar Updates`_ section of this
proposal for details.
The key ``await`` difference from ``yield`` and ``yield from``
operators is that *await expressions* do not require parentheses around
them most of the times.
Also, ``yield from`` allows any expression as its argument, including Also, ``yield from`` allows any expression as its argument, including
expressions like ``yield from a() + b()``, that would be parsed as expressions like ``yield from a() + b()``, that would be parsed as
@ -186,7 +217,8 @@ operators.
+------------------------------+-----------------------------------+ +------------------------------+-----------------------------------+
| Operator | Description | | Operator | Description |
+==============================+===================================+ +==============================+===================================+
| ``yield``, ``yield from`` | Yield expression | | ``yield`` ``x``, | Yield expression |
| ``yield from`` ``x`` | |
+------------------------------+-----------------------------------+ +------------------------------+-----------------------------------+
| ``lambda`` | Lambda expression | | ``lambda`` | Lambda expression |
+------------------------------+-----------------------------------+ +------------------------------+-----------------------------------+
@ -221,7 +253,7 @@ operators.
+------------------------------+-----------------------------------+ +------------------------------+-----------------------------------+
| ``**`` | Exponentiation | | ``**`` | Exponentiation |
+------------------------------+-----------------------------------+ +------------------------------+-----------------------------------+
| ``await`` | Await expression | | ``await`` ``x`` | Await expression |
+------------------------------+-----------------------------------+ +------------------------------+-----------------------------------+
| ``x[index]``, | Subscription, slicing, | | ``x[index]``, | Subscription, slicing, |
| ``x[index:index]``, | call, attribute reference | | ``x[index:index]``, | call, attribute reference |
@ -234,8 +266,6 @@ operators.
| ``{expressions...}`` | set display | | ``{expressions...}`` | set display |
+------------------------------+-----------------------------------+ +------------------------------+-----------------------------------+
See `Grammar Updates`_ section for details.
Examples of "await" expressions Examples of "await" expressions
''''''''''''''''''''''''''''''' '''''''''''''''''''''''''''''''
@ -266,8 +296,6 @@ Expression Should be written as
``await -coro()`` ``await (-coro())`` ``await -coro()`` ``await (-coro())``
================================== ================================== ================================== ==================================
See `Grammar Updates`_ section for details.
Asynchronous Context Managers and "async with" Asynchronous Context Managers and "async with"
---------------------------------------------- ----------------------------------------------
@ -306,18 +334,13 @@ which is semantically equivalent to::
exc = True exc = True
try: try:
try: VAR = await aenter
VAR = await aenter BLOCK
BLOCK except:
except: if not await aexit(mgr, *sys.exc_info()):
exc = False raise
exit_res = await aexit(mgr, *sys.exc_info()) else:
if not exit_res: await aexit(mgr, None, None, None)
raise
finally:
if exc:
await aexit(mgr, None, None, None)
As with regular ``with`` statements, it is possible to specify multiple As with regular ``with`` statements, it is possible to specify multiple
@ -325,13 +348,13 @@ context managers in a single ``async with`` statement.
It is an error to pass a regular context manager without ``__aenter__`` It is an error to pass a regular context manager without ``__aenter__``
and ``__aexit__`` methods to ``async with``. It is a ``SyntaxError`` and ``__aexit__`` methods to ``async with``. It is a ``SyntaxError``
to use ``async with`` outside of a coroutine. to use ``async with`` outside of an ``async def`` function.
Example Example
''''''' '''''''
With asynchronous context managers it is easy to implement proper With *asynchronous context managers* it is easy to implement proper
database transaction managers for coroutines:: database transaction managers for coroutines::
async def commit(session, data): async def commit(session, data):
@ -416,7 +439,7 @@ which is semantically equivalent to::
It is a ``TypeError`` to pass a regular iterable without ``__aiter__`` It is a ``TypeError`` to pass a regular iterable without ``__aiter__``
method to ``async for``. It is a ``SyntaxError`` to use ``async for`` method to ``async for``. It is a ``SyntaxError`` to use ``async for``
outside of a coroutine. outside of an ``async def`` function.
As for with regular ``for`` statement, ``async for`` has an optional As for with regular ``for`` statement, ``async for`` has an optional
``else`` clause. ``else`` clause.
@ -536,11 +559,61 @@ Moreover, with semantics from PEP 479, all ``StopIteration`` exceptions
raised in coroutines are wrapped in ``RuntimeError``. raised in coroutines are wrapped in ``RuntimeError``.
Coroutine objects
-----------------
Differences from generators
'''''''''''''''''''''''''''
This section applies only to *native coroutines* with
``CO_NATIVE_COROUTINE`` flag, i.e. defined with the new ``async def``
syntax.
**The behavior of existing *generator-based coroutines* in asyncio
remains unchanged.**
Great effort has been made to make sure that coroutines and
generators are treated as distinct concepts:
1. *Native coroutine objects* do not implement ``__iter__`` and
``__next__`` methods. Therefore, they cannot be iterated over or
passed to ``iter()``, ``list()``, ``tuple()`` and other built-ins.
They also cannot be used in a ``for..in`` loop.
An attempt to use ``__iter__`` or ``__next__`` on a *native
coroutine object* will result in a ``TypeError``.
2. *Plain generators* cannot ``yield from`` *native coroutine objects*:
doing so will result in a ``TypeError``.
3. *generator-based coroutines* (for asyncio code must be decorated
with ``@asyncio.coroutine``) can ``yield from`` *native coroutine
objects*.
4. ``inspect.isgenerator()`` and ``inspect.isgeneratorfunction()``
return ``False`` for *native coroutine objects* and *native
coroutine functions*.
Coroutine object methods
''''''''''''''''''''''''
Coroutines are based on generators internally, thus they share the
implementation. Similarly to generator objects, coroutine objects have
``throw()``, ``send()`` and ``close()`` methods. ``StopIteration`` and
``GeneratorExit`` play the same role for coroutine objects (although
PEP 479 is enabled by default for coroutines). See PEP 342, PEP 380,
and Python Documentation [11]_ for details.
``throw()``, ``send()`` methods for coroutine objects are used to push
values and raise errors into *Future-like* objects.
Debugging Features Debugging Features
------------------ ------------------
One of the most frequent mistakes that people make when using A common beginner mistake is forgetting to use ``yield from`` on
generators as coroutines is forgetting to use ``yield from``:: coroutines::
@asyncio.coroutine @asyncio.coroutine
def useful(): def useful():
@ -590,65 +663,58 @@ Example::
# previously set wrapper # previously set wrapper
assert not isinstance(debug_me(), asyncio.CoroWrapper) assert not isinstance(debug_me(), asyncio.CoroWrapper)
If ``sys.set_coroutine_wrapper()`` is called twice, the new wrapper
replaces the previous wrapper. ``sys.set_coroutine_wrapper(None)``
unsets the wrapper.
New Standard Library Functions
------------------------------
inspect.iscoroutine() and inspect.iscoroutineobject() * ``types.coroutine(gen)``. See `types.coroutine()`_ section for
----------------------------------------------------- details.
Two new functions are added to the ``inspect`` module:
* ``inspect.iscoroutine(obj)`` returns ``True`` if ``obj`` is a * ``inspect.iscoroutine(obj)`` returns ``True`` if ``obj`` is a
coroutine object. *coroutine object*.
* ``inspect.iscoroutinefunction(obj)`` returns ``True`` is ``obj`` is a * ``inspect.iscoroutinefunction(obj)`` returns ``True`` if ``obj`` is a
coroutine function. *coroutine function*.
* ``inspect.isawaitable(obj)`` returns ``True`` if ``obj`` can be used
in ``await`` expression. See `Await Expression`_ for details.
Differences between coroutines and generators * ``sys.set_coroutine_wrapper(wraper)`` allows to intercept creation of
--------------------------------------------- *coroutine objects*. ``wraper`` must be a callable that accepts one
argument: a *coroutine object* or ``None``. ``None`` resets the
wrapper. If called twice, the new wrapper replaces the previous one.
See `Debugging Features`_ for more details.
A great effort has been made to make sure that coroutines and * ``sys.get_coroutine_wrapper()`` returns the current wrapper object.
generators are separate concepts: Returns ``None`` if no wrapper was set. See `Debugging Features`_
for more details.
1. Coroutine objects do not implement ``__iter__`` and ``__next__``
methods. Therefore they cannot be iterated over or passed to
``iter()``, ``list()``, ``tuple()`` and other built-ins. They
also cannot be used in a ``for..in`` loop.
2. ``yield from`` does not accept coroutine objects (unless it is used
in a generator-based coroutine decorated with ``types.coroutine``.)
3. ``yield from`` does not accept coroutine objects from plain Python
generators (*not* generator-based coroutines.)
4. ``inspect.isgenerator()`` and ``inspect.isgeneratorfunction()``
return ``False`` for coroutine objects and coroutine functions.
Coroutine objects
-----------------
Coroutines are based on generators internally, thus they share the
implementation. Similarly to generator objects, coroutine objects have
``throw``, ``send`` and ``close`` methods. ``StopIteration`` and
``GeneratorExit`` play the same role for coroutine objects (although
PEP 479 is enabled by default for coroutines).
Glossary Glossary
======== ========
:Native coroutine:
A coroutine function is declared with ``async def``. It uses
``await`` and ``return value``; see `New Coroutine Declaration
Syntax`_ for details.
:Native coroutine object:
Returned from a native coroutine function. See `Await Expression`_
for details.
:Generator-based coroutine:
Coroutines based on generator syntax. Most common example are
functions decorated with ``@asyncio.coroutine``.
:Generator-based coroutine object:
Returned from a generator-based coroutine function.
:Coroutine: :Coroutine:
A coroutine function, or just "coroutine", is declared with ``async Either *native coroutine* or *generator-based coroutine*.
def``. It uses ``await`` and ``return value``; see `New Coroutine
Declaration Syntax`_ for details.
:Coroutine object: :Coroutine object:
Returned from a coroutine function. See `Await Expression`_ for Either *native coroutine object* or *generator-based coroutine
details. object*.
:Future-like object: :Future-like object:
An object with an ``__await__`` method, or a C object with An object with an ``__await__`` method, or a C object with
@ -662,10 +728,6 @@ Glossary
A *Future-like* object or a *coroutine object*. See `Await A *Future-like* object or a *coroutine object*. See `Await
Expression`_ for details. Expression`_ for details.
:Generator-based coroutine:
Coroutines based in generator syntax. Most common example is
``@asyncio.coroutine``.
:Asynchronous context manager: :Asynchronous context manager:
An asynchronous context manager has ``__aenter__`` and ``__aexit__`` An asynchronous context manager has ``__aenter__`` and ``__aexit__``
methods and can be used with ``async with``. See `Asynchronous methods and can be used with ``async with``. See `Asynchronous
@ -696,7 +758,7 @@ generator yield, yield from, return value await
Where: Where:
* "async def func": coroutine; * "async def func": native coroutine;
* "async def __a*__": ``__aiter__``, ``__anext__``, ``__aenter__``, * "async def __a*__": ``__aiter__``, ``__anext__``, ``__aenter__``,
``__aexit__`` defined with the ``async`` keyword; ``__aexit__`` defined with the ``async`` keyword;
@ -720,12 +782,13 @@ keywords, it was decided to modify ``tokenizer.c`` in such a way, that
it: it:
* recognizes ``async def`` name tokens combination (start of a * recognizes ``async def`` name tokens combination (start of a
coroutine); native coroutine);
* keeps track of regular functions and coroutines; * keeps track of regular functions and native coroutines;
* replaces ``'async'`` token with ``ASYNC`` and ``'await'`` token with * replaces ``'async'`` token with ``ASYNC`` and ``'await'`` token with
``AWAIT`` when in the process of yielding tokens for coroutines. ``AWAIT`` when in the process of yielding tokens for native
coroutines.
This approach allows for seamless combination of new syntax features This approach allows for seamless combination of new syntax features
(all of them available only in ``async`` functions) with any existing (all of them available only in ``async`` functions) with any existing
@ -749,6 +812,34 @@ Backwards Compatibility
This proposal preserves 100% backwards compatibility. This proposal preserves 100% backwards compatibility.
asyncio
-------
``asyncio`` module was adapted and tested to work with coroutines and
new statements. Backwards compatibility is 100% preserved, i.e. all
existing code will work as-is.
The required changes are mainly:
1. Modify ``@asyncio.coroutine`` decorator to use new
``types.coroutine()`` function.
2. Add ``__await__ = __iter__`` line to ``asyncio.Future`` class.
3. Add ``ensure_task()`` as an alias for ``async()`` function.
Deprecate ``async()`` function.
Migration strategy
''''''''''''''''''
Because *plain generators* cannot ``yield from`` *native coroutine
objects* (see `Differences from generators`_ section for more details),
it is advised to make sure that all generator-based coroutines are
decorated with ``@asyncio.coroutine`` *before* starting to use the new
syntax.
Grammar Updates Grammar Updates
--------------- ---------------
@ -811,23 +902,6 @@ and 3.6. In 3.7 we will transform them to proper keywords. Making
for people to port their code to Python 3. for people to port their code to Python 3.
asyncio
-------
``asyncio`` module was adapted and tested to work with coroutines and
new statements. Backwards compatibility is 100% preserved.
The required changes are mainly:
1. Modify ``@asyncio.coroutine`` decorator to use new
``types.coroutine()`` function.
2. Add ``__await__ = __iter__`` line to ``asyncio.Future`` class.
3. Add ``ensure_task()`` as an alias for ``async()`` function.
Deprecate ``async()`` function.
Design Considerations Design Considerations
===================== =====================
@ -928,31 +1002,6 @@ in the implementation of current generator objects. This is a matter
for a separate PEP. for a separate PEP.
No implicit wrapping in Futures
-------------------------------
There is a proposal to add similar mechanism to ECMAScript 7 [2]_. A
key difference is that JavaScript "async functions" always return a
Promise. While this approach has some advantages, it also implies that
a new Promise object is created on each "async function" invocation.
We could implement a similar functionality in Python, by wrapping all
coroutines in a Future object, but this has the following
disadvantages:
1. Performance. A new Future object would be instantiated on each
coroutine call. Moreover, this makes implementation of ``await``
expressions slower (disabling optimizations of ``yield from``).
2. A new built-in ``Future`` object would need to be added.
3. Coming up with a generic ``Future`` interface that is usable for any
use case in any framework is a very hard to solve problem.
4. It is not a feature that is used frequently, when most of the code
is coroutines.
Why "async" and "await" keywords Why "async" and "await" keywords
-------------------------------- --------------------------------
@ -978,8 +1027,8 @@ 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__" is a coroutine Why "__aiter__" returns awaitable
------------------------------ ---------------------------------
In principle, ``__aiter__`` could be a regular function. There are In principle, ``__aiter__`` could be a regular function. There are
several good reasons to make it a coroutine: several good reasons to make it a coroutine:
@ -1098,9 +1147,9 @@ This approach has the following downsides:
returning a Future-like objects from ``__enter__`` and/or returning a Future-like objects from ``__enter__`` and/or
``__exit__`` in Python <= 3.4; ``__exit__`` in Python <= 3.4;
* one of the main points of this proposal is to make coroutines as * one of the main points of this proposal is to make native coroutines
simple and foolproof as possible, hence the clear separation of the as simple and foolproof as possible, hence the clear separation of
protocols. the protocols.
Why not reuse existing "for" and "with" statements Why not reuse existing "for" and "with" statements
@ -1120,8 +1169,8 @@ Syntax for asynchronous comprehensions could be provided, but this
construct is outside of the scope of this PEP. construct is outside of the scope of this PEP.
Async lambdas Async lambda functions
------------- ----------------------
Syntax for asynchronous lambda functions could be provided, but this Syntax for asynchronous lambda functions could be provided, but this
construct is outside of the scope of this PEP. construct is outside of the scope of this PEP.
@ -1236,9 +1285,11 @@ List of high-level changes and new protocols
6. New functions: ``sys.set_coroutine_wrapper(callback)``, 6. New functions: ``sys.set_coroutine_wrapper(callback)``,
``sys.get_coroutine_wrapper()``, ``types.coroutine(gen)``, ``sys.get_coroutine_wrapper()``, ``types.coroutine(gen)``,
``inspect.iscoroutinefunction()``, and ``inspect.iscoroutine()``. ``inspect.iscoroutinefunction()``, ``inspect.iscoroutine()``,
and ``inspect.isawaitable()``.
7. New ``CO_COROUTINE`` bit flag for code objects. 7. New ``CO_COROUTINE`` and ``CO_NATIVE_COROUTINE`` bit flags for code
objects.
While the list of changes and new things is not short, it is important While the list of changes and new things is not short, it is important
to understand, that most users will not use these features directly. to understand, that most users will not use these features directly.
@ -1305,6 +1356,9 @@ References
.. [10] http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3722.pdf (PDF) .. [10] http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3722.pdf (PDF)
.. [11] https://docs.python.org/3/reference/expressions.html#generator-iterator-methods
.. [12] https://docs.python.org/3/reference/expressions.html#primaries
Acknowledgments Acknowledgments
=============== ===============