diff --git a/pep-0492.txt b/pep-0492.txt index 2c49bcac8..1ec9a8660 100644 --- a/pep-0492.txt +++ b/pep-0492.txt @@ -8,7 +8,7 @@ Type: Standards Track Content-Type: text/x-rst Created: 09-Apr-2015 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 @@ -57,8 +57,7 @@ Specification ============= This proposal introduces new syntax and semantics to enhance coroutine -support in Python, it does not change the internal implementation of -coroutines, which are still based on generators. +support in Python. This specification presumes knowledge of the implementation of 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 specification). -From this point in this document we use the word *coroutine* to refer -to functions declared using the new syntax. *generator-based +From this point in this document we use the word *native coroutine* to +refer to functions declared using the new syntax. *generator-based 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 -------------------------------- -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): pass -Key properties of coroutines: +Key properties of *native coroutines*: * ``async def`` functions are always coroutines, even if they do not contain ``await`` expressions. @@ -88,10 +88,16 @@ Key properties of coroutines: * It is a ``SyntaxError`` to have ``yield`` or ``yield from`` expressions in an ``async`` function. -* Internally, a new code object flag - ``CO_COROUTINE`` - is introduced - to enable runtime detection of coroutines (and migrating existing - code). All coroutines have both ``CO_COROUTINE`` and ``CO_GENERATOR`` - flags set. +* Internally, two new code object flags were introduced: + + - ``CO_COROUTINE`` is used to enable runtime detection of + *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*; similarly, coroutines return a *coroutine object*. @@ -100,15 +106,25 @@ Key properties of coroutines: and are replaced with a ``RuntimeError``. For regular generators such behavior requires a future import (see PEP 479). +* See also `Coroutine objects`_ section. + types.coroutine() ----------------- A new function ``coroutine(gen)`` is added to the ``types`` module. It -applies ``CO_COROUTINE`` flag to the passed generator-function's code -object, making it to return a *coroutine object* when called. +allows interoperability between existing *generator-based coroutines* +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 @@ -129,7 +145,9 @@ It uses the ``yield from`` implementation with an extra step of validating its argument. ``await`` only accepts an *awaitable*, which 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()``. * An object with an ``__await__`` method returning an iterator. @@ -142,7 +160,7 @@ can be one of: explanation.) 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 ``__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, 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 to an ``await`` expression. @@ -169,11 +189,22 @@ to an ``await`` expression. Updated operator precedence table ''''''''''''''''''''''''''''''''' -``await`` keyword is defined differently from ``yield`` and ``yield -from`` in the Grammar. +``await`` keyword is defined as follows:: -The key difference is that *await expressions* do not require -parentheses around them most of the times. + power ::= await ["**" u_expr] + 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 expressions like ``yield from a() + b()``, that would be parsed as @@ -186,7 +217,8 @@ operators. +------------------------------+-----------------------------------+ | Operator | Description | +==============================+===================================+ -| ``yield``, ``yield from`` | Yield expression | +| ``yield`` ``x``, | Yield expression | +| ``yield from`` ``x`` | | +------------------------------+-----------------------------------+ | ``lambda`` | Lambda expression | +------------------------------+-----------------------------------+ @@ -221,7 +253,7 @@ operators. +------------------------------+-----------------------------------+ | ``**`` | Exponentiation | +------------------------------+-----------------------------------+ -| ``await`` | Await expression | +| ``await`` ``x`` | Await expression | +------------------------------+-----------------------------------+ | ``x[index]``, | Subscription, slicing, | | ``x[index:index]``, | call, attribute reference | @@ -234,8 +266,6 @@ operators. | ``{expressions...}`` | set display | +------------------------------+-----------------------------------+ -See `Grammar Updates`_ section for details. - Examples of "await" expressions ''''''''''''''''''''''''''''''' @@ -266,8 +296,6 @@ Expression Should be written as ``await -coro()`` ``await (-coro())`` ================================== ================================== -See `Grammar Updates`_ section for details. - Asynchronous Context Managers and "async with" ---------------------------------------------- @@ -306,18 +334,13 @@ which is semantically equivalent to:: exc = True try: - try: - VAR = await aenter - BLOCK - except: - exc = False - exit_res = await aexit(mgr, *sys.exc_info()) - if not exit_res: - raise - - finally: - if exc: - await aexit(mgr, None, None, None) + VAR = await aenter + BLOCK + except: + if not await aexit(mgr, *sys.exc_info()): + raise + else: + await aexit(mgr, None, None, None) 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__`` 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 ''''''' -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:: 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__`` 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 ``else`` clause. @@ -536,11 +559,61 @@ Moreover, with semantics from PEP 479, all ``StopIteration`` exceptions 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 ------------------ -One of the most frequent mistakes that people make when using -generators as coroutines is forgetting to use ``yield from``:: +A common beginner mistake is forgetting to use ``yield from`` on +coroutines:: @asyncio.coroutine def useful(): @@ -590,65 +663,58 @@ Example:: # previously set wrapper 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() ------------------------------------------------------ - -Two new functions are added to the ``inspect`` module: +* ``types.coroutine(gen)``. See `types.coroutine()`_ section for + details. * ``inspect.iscoroutine(obj)`` returns ``True`` if ``obj`` is a - coroutine object. + *coroutine object*. -* ``inspect.iscoroutinefunction(obj)`` returns ``True`` is ``obj`` is a - coroutine function. +* ``inspect.iscoroutinefunction(obj)`` returns ``True`` if ``obj`` is a + *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 -generators are separate concepts: - -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). +* ``sys.get_coroutine_wrapper()`` returns the current wrapper object. + Returns ``None`` if no wrapper was set. See `Debugging Features`_ + for more details. 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: - A coroutine function, or just "coroutine", is declared with ``async - def``. It uses ``await`` and ``return value``; see `New Coroutine - Declaration Syntax`_ for details. + Either *native coroutine* or *generator-based coroutine*. :Coroutine object: - Returned from a coroutine function. See `Await Expression`_ for - details. + Either *native coroutine object* or *generator-based coroutine + object*. :Future-like object: 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 Expression`_ for details. -:Generator-based coroutine: - Coroutines based in generator syntax. Most common example is - ``@asyncio.coroutine``. - :Asynchronous context manager: An asynchronous context manager has ``__aenter__`` and ``__aexit__`` methods and can be used with ``async with``. See `Asynchronous @@ -696,7 +758,7 @@ generator yield, yield from, return value await Where: -* "async def func": coroutine; +* "async def func": native coroutine; * "async def __a*__": ``__aiter__``, ``__anext__``, ``__aenter__``, ``__aexit__`` defined with the ``async`` keyword; @@ -720,12 +782,13 @@ keywords, it was decided to modify ``tokenizer.c`` in such a way, that it: * 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 - ``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 (all of them available only in ``async`` functions) with any existing @@ -749,6 +812,34 @@ 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 --------------- @@ -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. -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 ===================== @@ -928,31 +1002,6 @@ in the implementation of current generator objects. This is a matter 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 -------------------------------- @@ -978,8 +1027,8 @@ async/await, and because it makes working with many languages in one 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 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 ``__exit__`` in Python <= 3.4; -* one of the main points of this proposal is to make coroutines as - simple and foolproof as possible, hence the clear separation of the - protocols. +* one of the main points of this proposal is to make native coroutines + as simple and foolproof as possible, hence the clear separation of + the protocols. 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. -Async lambdas -------------- +Async lambda functions +---------------------- Syntax for asynchronous lambda functions could be provided, but this 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)``, ``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 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) +.. [11] https://docs.python.org/3/reference/expressions.html#generator-iterator-methods + +.. [12] https://docs.python.org/3/reference/expressions.html#primaries Acknowledgments ===============