The growth of Internet and general connectivity has triggered theproportionate need for responsive and scalable code. This proposalaims to answer that need by making writing explicitly asynchronous,concurrent Python code easier and more Pythonic.
It is proposed to makecoroutines a proper standalone concept inPython, and introduce new supporting syntax. The ultimate goalis to help establish a common, easily approachable, mentalmodel of asynchronous programming in Python and make it as close tosynchronous programming as possible.
This PEP assumes that the asynchronous tasks are scheduled andcoordinated by an Event Loop similar to that of stdlib moduleasyncio.events.AbstractEventLoop. While the PEP is not tied to anyspecific Event Loop implementation, it is relevant only to the kind ofcoroutine that usesyield as a signal to the scheduler, indicatingthat the coroutine will be waiting until an event (such as IO) iscompleted.
We believe that the changes proposed here will help keep Pythonrelevant and competitive in a quickly growing area of asynchronousprogramming, as many other languages have adopted, or are planning toadopt, similar features:[2],[5],[6],[7],[8],[10].
This change was implemented based primarily due to problemsencountered attempting to integrate support for native coroutinesinto the Tornado web server (reported in[18]).
__aiter__ protocol was updated.Before 3.5.2,__aiter__ was expected to return anawaitableresolving to anasynchronous 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 aPendingDeprecationWarning.
In CPython 3.6, the old__aiter__ protocol will still besupported with aDeprecationWarning being raised.
In CPython 3.7, the old__aiter__ protocol will no longer besupported: aRuntimeError will be raised if__aiter__returns anything but an asynchronous iterator.
Current Python supports implementing coroutines via generators (PEP342), further enhanced by theyieldfrom syntax introduced in PEP380. This approach has a number of shortcomings:
yield oryieldfrom statements in itsbody, which canlead to unobvious errors when such statements appear in or disappearfrom function body during refactoring.yield is allowed syntactically, limiting the usefulness ofsyntactic features, such aswith andfor statements.This proposal makes coroutines a native Python language feature, andclearly separates them from generators. This removesgenerator/coroutine ambiguity, and makes it possible to reliably definecoroutines without reliance on a specific library. This also enableslinters and IDEs to improve static code analysis and refactoring.
Native coroutines and the associated new syntax features make itpossible to define context manager and iteration protocols inasynchronous terms. As shown later in this proposal, the newasyncwith statement lets Python programs perform asynchronous calls whenentering and exiting a runtime context, and the newasyncforstatement makes it possible to perform asynchronous calls in iterators.
This proposal introduces new syntax and semantics to enhance coroutinesupport in Python.
This specification presumes knowledge of the implementation ofcoroutines in Python (PEP 342 andPEP 380). Motivation for the syntaxchanges proposed here comes from the asyncio framework (PEP 3156) andthe “Cofunctions” proposal (PEP 3152, now rejected in favor of thisspecification).
From this point in this document we use the wordnative coroutine torefer to functions declared using the new syntax.generator-basedcoroutine is used where necessary to refer to coroutines that arebased on generator syntax.coroutine is used in contexts where bothdefinitions are applicable.
The following new syntax is used to declare anative coroutine:
asyncdefread_data(db):pass
Key properties ofcoroutines:
asyncdef functions are always coroutines, even if they do notcontainawait expressions.SyntaxError to haveyield oryieldfromexpressions in anasync function.CO_COROUTINE is used to marknative coroutines(defined with new syntax).CO_ITERABLE_COROUTINE is used to makegenerator-basedcoroutines compatible withnative coroutines (set bytypes.coroutine() function).StopIteration exceptions are not propagated out of coroutines,and are replaced with aRuntimeError. For regular generatorssuch behavior requires a future import (seePEP 479).RuntimeWarningis raised if it was never awaited on (see alsoDebugging Features).A new functioncoroutine(fn) is added to thetypes module. Itallows interoperability between existinggenerator-based coroutinesin asyncio andnative coroutines introduced by this PEP:
@types.coroutinedefprocess_data(db):data=yield fromread_data(db)...
The function appliesCO_ITERABLE_COROUTINE flag togenerator-function’s code object, making it return acoroutine object.
Iffn is not agenerator function, it is wrapped. If it returnsagenerator, it will be wrapped in anawaitable proxy object(see below the definition of awaitable objects).
Note, that theCO_COROUTINE flag is not applied bytypes.coroutine() to make it possible to separatenativecoroutines defined with new syntax, fromgenerator-based coroutines.
The following newawait expression is used to obtain a result ofcoroutine execution:
asyncdefread_data(db):data=awaitdb.fetch('SELECT ...')...
await, similarly toyieldfrom, suspends execution ofread_data coroutine untildb.fetchawaitable completes andreturns the result data.
It uses theyieldfrom implementation with an extra step ofvalidating its argument.await only accepts anawaitable, whichcan be one of:
types.coroutine().__await__ method returning an iterator.Anyyieldfrom chain of calls ends with ayield. This is afundamental mechanism of howFutures are implemented. Since,internally, coroutines are a special kind of generators, everyawait is suspended by ayield somewhere down the chain ofawait calls (please refer toPEP 3156 for a detailedexplanation).
To enable this behavior for coroutines, a new magic method called__await__ is added. In asyncio, for instance, to enableFutureobjects inawait statements, the only change is to add__await__=__iter__ line toasyncio.Future class.
Objects with__await__ method are calledFuture-like objects inthe rest of this PEP.
It is aTypeError if__await__ returns anything but aniterator.
tp_as_async.am_awaitfunction, returning aniterator (similar to__await__ method).It is aSyntaxError to useawait outside of anasyncdeffunction (like it is aSyntaxError to useyield outside ofdef function).
It is aTypeError to pass anything other than anawaitable objectto anawait expression.
await keyword is defined as follows:
power::=await["**"u_expr]await::=["await"]primary
where “primary” represents the most tightly bound operations of thelanguage. Its syntax is:
primary::=atom|attributeref|subscription|slicing|call
See Python Documentation[12] andGrammar Updates section of thisproposal for details.
The keyawait difference fromyield andyieldfromoperators is thatawait expressions do not require parentheses aroundthem most of the times.
Also,yieldfrom allows any expression as its argument, includingexpressions likeyieldfroma()+b(), that would be parsed asyieldfrom(a()+b()), which is almost always a bug. In general,the result of any arithmetic operation is not anawaitable object.To avoid this kind of mistakes, it was decided to makeawaitprecedence lower than[],(), and., but higher than**operators.
| Operator | Description |
|---|---|
yieldx,yieldfromx | Yield expression |
lambda | Lambda expression |
if –else | Conditional expression |
or | Boolean OR |
and | Boolean AND |
notx | Boolean NOT |
in,notin,is,isnot,<,<=,>,>=,!=,== | Comparisons, including membershiptests and identity tests |
| | Bitwise OR |
^ | Bitwise XOR |
& | Bitwise AND |
<<,>> | Shifts |
+,- | Addition and subtraction |
*,@,/,//,% | Multiplication, matrixmultiplication, division,remainder |
+x,-x,~x | Positive, negative, bitwise NOT |
** | Exponentiation |
awaitx | Await expression |
x[index],x[index:index],x(arguments...),x.attribute | Subscription, slicing,call, attribute reference |
(expressions...),[expressions...],{key:value...},{expressions...} | Binding or tuple display,list display,dictionary display,set display |
Valid syntax examples:
| Expression | Will be parsed as |
|---|---|
ifawaitfut:pass | if(awaitfut):pass |
ifawaitfut+1:pass | if(awaitfut)+1:pass |
pair=awaitfut,'spam' | pair=(awaitfut),'spam' |
withawaitfut,open():pass | with(awaitfut),open():pass |
awaitfoo()['spam'].baz()() | await(foo()['spam'].baz()()) |
returnawaitcoro() | return(awaitcoro()) |
res=awaitcoro()**2 | res=(awaitcoro())**2 |
func(a1=awaitcoro(),a2=0) | func(a1=(awaitcoro()),a2=0) |
awaitfoo()+awaitbar() | (awaitfoo())+(awaitbar()) |
-awaitfoo() | -(awaitfoo()) |
Invalid syntax examples:
| Expression | Should be written as |
|---|---|
awaitawaitcoro() | await(awaitcoro()) |
await-coro() | await(-coro()) |
Anasynchronous context manager is a context manager that is able tosuspend execution in itsenter andexit methods.
To make this possible, a new protocol for asynchronous context managersis proposed. Two new magic methods are added:__aenter__ and__aexit__. Both must return anawaitable.
An example of an asynchronous context manager:
classAsyncContextManager:asyncdef__aenter__(self):awaitlog('entering context')asyncdef__aexit__(self,exc_type,exc,tb):awaitlog('exiting context')
A new statement for asynchronous context managers is proposed:
asyncwithEXPRasVAR:BLOCK
which is semantically equivalent to:
mgr=(EXPR)aexit=type(mgr).__aexit__aenter=type(mgr).__aenter__VAR=awaitaenter(mgr)try:BLOCKexcept:ifnotawaitaexit(mgr,*sys.exc_info()):raiseelse:awaitaexit(mgr,None,None,None)
As with regularwith statements, it is possible to specify multiplecontext managers in a singleasyncwith statement.
It is an error to pass a regular context manager without__aenter__and__aexit__ methods toasyncwith. It is aSyntaxErrorto useasyncwith outside of anasyncdef function.
Withasynchronous context managers it is easy to implement properdatabase transaction managers for coroutines:
asyncdefcommit(session,data):...asyncwithsession.transaction():...awaitsession.update(data)...
Code that needs locking also looks lighter:
asyncwithlock:...
instead of:
with(yield fromlock):...
Anasynchronous iterable is able to call asynchronous code in itsiter implementation, andasynchronous iterator can callasynchronous code in itsnext method. To support asynchronousiteration:
__aiter__ method (or, if definedwith CPython C API,tp_as_async.am_aiter slot) returning anasynchronous iterator object.__anext__method (or, if defined with CPython C API,tp_as_async.am_anextslot) returning anawaitable.__anext__ must raise aStopAsyncIterationexception.An example of asynchronous iterable:
classAsyncIterable:def__aiter__(self):returnselfasyncdef__anext__(self):data=awaitself.fetch_data()ifdata:returndataelse:raiseStopAsyncIterationasyncdeffetch_data(self):...
A new statement for iterating through asynchronous iterators isproposed:
asyncforTARGETinITER:BLOCKelse:BLOCK2
which is semantically equivalent to:
iter=(ITER)iter=type(iter).__aiter__(iter)running=Truewhilerunning:try:TARGET=awaittype(iter).__anext__(iter)exceptStopAsyncIteration:running=Falseelse:BLOCKelse:BLOCK2
It is aTypeError to pass a regular iterable without__aiter__method toasyncfor. It is aSyntaxError to useasyncforoutside of anasyncdef function.
As for with regularfor statement,asyncfor has an optionalelse clause.
With asynchronous iteration protocol it is possible to asynchronouslybuffer data during iteration:
asyncfordataincursor:...
Wherecursor is an asynchronous iterator that prefetchesN rowsof data from a database after everyN iterations.
The following code illustrates new asynchronous iteration protocol:
classCursor:def__init__(self):self.buffer=collections.deque()asyncdef_prefetch(self):...def__aiter__(self):returnselfasyncdef__anext__(self):ifnotself.buffer:self.buffer=awaitself._prefetch()ifnotself.buffer:raiseStopAsyncIterationreturnself.buffer.popleft()
then theCursor class can be used as follows:
asyncforrowinCursor():print(row)
which would be equivalent to the following code:
i=Cursor().__aiter__()whileTrue:try:row=awaiti.__anext__()exceptStopAsyncIteration:breakelse:print(row)
The following is a utility class that transforms a regular iterable toan asynchronous one. While this is not a very useful thing to do, thecode illustrates the relationship between regular and asynchronousiterators.
classAsyncIteratorWrapper:def__init__(self,obj):self._it=iter(obj)def__aiter__(self):returnselfasyncdef__anext__(self):try:value=next(self._it)exceptStopIteration:raiseStopAsyncIterationreturnvalueasyncforletterinAsyncIteratorWrapper("abc"):print(letter)
Coroutines are still based on generators internally. So, before PEP479, there was no fundamental difference between
defg1():yield fromfutreturn'spam'
and
defg2():yield fromfutraiseStopIteration('spam')
And sincePEP 479 is accepted and enabled by default for coroutines,the following example will have itsStopIteration wrapped into aRuntimeError
asyncdefa1():awaitfutraiseStopIteration('spam')
The only way to tell the outside code that the iteration has ended isto raise something other thanStopIteration. Therefore, a newbuilt-in exception classStopAsyncIteration was added.
Moreover, with semantics fromPEP 479, allStopIteration exceptionsraised in coroutines are wrapped inRuntimeError.
This section applies only tonative coroutines withCO_COROUTINEflag, i.e. defined with the newasyncdef syntax.
The behavior of existing *generator-based coroutines* in asyncioremains unchanged.
Great effort has been made to make sure that coroutines andgenerators are treated as distinct concepts:
__iter__ and__next__ methods. Therefore, they cannot be iterated over orpassed toiter(),list(),tuple() and other built-ins.They also cannot be used in afor..in loop.An attempt to use__iter__ or__next__ on anativecoroutine object will result in aTypeError.
yieldfromnative coroutines:doing so will result in aTypeError.@asyncio.coroutine[1]) canyieldfromnative coroutineobjects.inspect.isgenerator() andinspect.isgeneratorfunction()returnFalse fornative coroutine objects andnativecoroutine functions.Coroutines are based on generators internally, thus they share theimplementation. Similarly to generator objects,coroutines havethrow(),send() andclose() methods.StopIteration andGeneratorExit play the same role for coroutines (althoughPEP 479 is enabled by default for coroutines). SeePEP 342,PEP 380,and Python Documentation[11] for details.
throw(),send() methods forcoroutines are used to pushvalues and raise errors intoFuture-like objects.
A common beginner mistake is forgetting to useyieldfrom oncoroutines:
@asyncio.coroutinedefuseful():asyncio.sleep(1)# this will do nothing without 'yield from'
For debugging this kind of mistakes there is a special debug mode inasyncio, in which@coroutine decorator wraps all functions with aspecial object with a destructor logging a warning. Whenever a wrappedgenerator gets garbage collected, a detailed logging message isgenerated with information about where exactly the decorator functionwas defined, stack trace of where it was collected, etc. Wrapperobject also provides a convenient__repr__ function with detailedinformation about the generator.
The only problem is how to enable these debug capabilities. Sincedebug facilities should be a no-op in production mode,@coroutinedecorator makes the decision of whether to wrap or not to wrap based onan OS environment variablePYTHONASYNCIODEBUG. This way it ispossible to run asyncio programs with asyncio’s own functionsinstrumented.EventLoop.set_debug, a different debug facility, hasno impact on@coroutine decorator’s behavior.
With this proposal, coroutines is a native, distinct from generators,concept.In addition to aRuntimeWarning being raised oncoroutines that were never awaited, it is proposed to add two newfunctions to thesys module:set_coroutine_wrapper andget_coroutine_wrapper. This is to enable advanced debuggingfacilities in asyncio and other frameworks (such as displaying whereexactly coroutine was created, and a more detailed stack trace of whereit was garbage collected).
types.coroutine(gen). Seetypes.coroutine() section fordetails.inspect.iscoroutine(obj) returnsTrue ifobj is anative coroutine object.inspect.iscoroutinefunction(obj) returnsTrue ifobj is anative coroutine function.inspect.isawaitable(obj) returnsTrue ifobj is anawaitable.inspect.getcoroutinestate(coro) returns the current state ofanative coroutine object (mirrorsinspect.getfgeneratorstate(gen)).inspect.getcoroutinelocals(coro) returns the mapping of anative coroutine object’s local variables to their values(mirrorsinspect.getgeneratorlocals(gen)).sys.set_coroutine_wrapper(wrapper) allows to intercept creation ofnative coroutine objects.wrapper must be either a callable thataccepts one argument (acoroutine object), orNone.Noneresets the wrapper. If called twice, the new wrapper replaces theprevious one. The function is thread-specific. SeeDebuggingFeatures for more details.sys.get_coroutine_wrapper() returns the current wrapper object.ReturnsNone if no wrapper was set. The function isthread-specific. SeeDebugging Features for more details.In order to allow better integration with existing frameworks (such asTornado, see[13]) and compilers (such as Cython, see[16]), two newAbstract Base Classes (ABC) are added:
collections.abc.Awaitable ABC forFuture-like classes, thatimplement__await__ method.collections.abc.Coroutine ABC forcoroutine objects, thatimplementsend(value),throw(type,exc,tb),close() and__await__() methods.Note that generator-based coroutines withCO_ITERABLE_COROUTINEflag do not implement__await__ method, and therefore are notinstances ofcollections.abc.Coroutine andcollections.abc.Awaitable ABCs:
@types.coroutinedefgencoro():yieldassertnotisinstance(gencoro(),collections.abc.Coroutine)# however:assertinspect.isawaitable(gencoro())
To allow easy testing if objects support asynchronous iteration, twomore ABCs are added:
collections.abc.AsyncIterable – tests for__aiter__ method.collections.abc.AsyncIterator – tests for__aiter__ and__anext__ methods.asyncdef. It usesawait andreturnvalue; seeNew Coroutine DeclarationSyntax for details.@asyncio.coroutine.__await__ method, or a C object withtp_as_async->am_await function, returning aniterator. Can beconsumed by anawait expression in a coroutine. A coroutinewaiting for a Future-like object is suspended until the Future-likeobject’s__await__ completes, and returns the result. SeeAwait Expression for details.__aenter__ and__aexit__methods and can be used withasyncwith. SeeAsynchronousContext Managers and “async with” for details.__aiter__ method, which must return anasynchronous iterator object. Can be used withasyncfor.SeeAsynchronous Iterators and “async for” for details.__anext__ method. SeeAsynchronous Iterators and “async for” for details.To avoid backwards compatibility issues withasync andawaitkeywords, it was decided to modifytokenizer.c in such a way, thatit:
asyncdefNAME tokens combination;asyncdef block, it replaces'async'NAME token withASYNC, and'await'NAME token withAWAIT;def block, it yields'async' and'await'NAME tokens as is.This approach allows for seamless combination of new syntax features(all of them available only inasync functions) with any existingcode.
An example of having “async def” and “async” attribute in one piece ofcode:
classSpam:async=42asyncdefham():print(getattr(Spam,'async'))# The coroutine can be executed and will print '42'
This proposal preserves 100% backwards compatibility.
asyncio module was adapted and tested to work with coroutines andnew statements. Backwards compatibility is 100% preserved, i.e. allexisting code will work as-is.
The required changes are mainly:
@asyncio.coroutine decorator to use newtypes.coroutine() function.__await__=__iter__ line toasyncio.Future class.ensure_future() as an alias forasync() function.Deprecateasync() function.Becauseplain generators cannotyieldfromnative coroutineobjects (seeDifferences from generators section for more details),it is advised to make sure that all generator-based coroutines aredecorated with@asyncio.coroutinebefore starting to use the newsyntax.
There is no use ofawait names in CPython.
async is mostly used by asyncio. We are addressing this byrenamingasync() function toensure_future() (seeasynciosection for details).
Another use ofasync keyword is inLib/xml/dom/xmlbuilder.py,to define anasync=False attribute forDocumentLS class.There is no documentation or tests for it, it is not used anywhere elsein CPython. It is replaced with a getter, that raises aDeprecationWarning, advising to useasync_ attribute instead.‘async’ attribute is not documented and is not used in CPython codebase.
Grammar changes are fairly minimal:
decorated:decorators(classdef|funcdef|async_funcdef)async_funcdef:ASYNCfuncdefcompound_stmt:(if_stmt|while_stmt|for_stmt|try_stmt|with_stmt|funcdef|classdef|decorated|async_stmt)async_stmt:ASYNC(funcdef|with_stmt|for_stmt)power:atom_expr['**'factor]atom_expr:[AWAIT]atomtrailer*
async andawait names will be softly deprecated in CPython 3.5and 3.6. In 3.7 we will transform them to proper keywords. Makingasync andawait proper keywords before 3.7 might make it harderfor people to port their code to Python 3.
PEP 3152 by Gregory Ewing proposes a different mechanism for coroutines(called “cofunctions”). Some key points:
codef to declare acofunction.Cofunction isalways a generator, even if there is nococall expressionsinside it. Maps toasyncdef in this proposal.cocall to call acofunction. Can only be usedinside acofunction. Maps toawait in this proposal (withsome differences, see below).cocallkeyword.cocall grammatically requires parentheses after it:atom:cocall|<existingalternativesforatom>cocall:'cocall'atomcotrailer*'('[arglist]')'cotrailer:'['subscriptlist']'|'.'NAME
cocallf(*args,**kwds) is semantically equivalent toyieldfromf.__cocall__(*args,**kwds).Differences from this proposal:
__cocall__ in this PEP, which iscalled and its result is passed toyieldfrom in thecocallexpression.await keyword expects anawaitable object,validates the type, and executesyieldfrom on it. Although,__await__ method is similar to__cocall__, but is only usedto defineFuture-like objects.await is defined in almost the same way asyieldfrom in thegrammar (it is later enforced thatawait can only be insideasyncdef). It is possible to simply writeawaitfuture,whereascocall always requires parentheses.@asyncio.coroutine decorator to wrap all functions in an objectwith a__cocall__ method, or to implement__cocall__ ongenerators. To callcofunctions from existing generator-basedcoroutines it would be required to usecostart(cofunc,*args,**kwargs) built-in.cocallkeyword, it automatically prevents the common mistake of forgettingto useyieldfrom on generator-based coroutines. This proposaladdresses this problem with a different approach, seeDebuggingFeatures.cocall keyword to call a coroutineis that if is decided to implement coroutine-generators –coroutines withyield orasyncyield expressions – wewouldn’t need acocall keyword to call them. So we’ll end uphaving__cocall__ and no__call__ for regular coroutines,and having__call__ and no__cocall__ forcoroutine-generators.The following code:
awaitfutawaitfunction_returning_future()awaitasyncio.gather(coro1(arg1,arg2),coro2(arg1,arg2))
would look like:
cocallfut()# or cocall costart(fut)cocall(function_returning_future())()cocallasyncio.gather(costart(coro1,arg1,arg2),costart(coro2,arg1,arg2))
asyncfor andasyncwith in PEP3152.Withasyncfor keyword it is desirable to have a concept of acoroutine-generator – a coroutine withyield andyieldfromexpressions. To avoid any ambiguity with regular generators, we wouldlikely require to have anasync keyword beforeyield, andasyncyieldfrom would raise aStopAsyncIteration exception.
While it is possible to implement coroutine-generators, we believe thatthey are out of scope of this proposal. It is an advanced concept thatshould be carefully considered and balanced, with a non-trivial changesin the implementation of current generator objects. This is a matterfor a separate PEP.
async/await is not a new concept in programming languages:
This is a huge benefit, as some users already have experience withasync/await, and because it makes working with many languages in oneproject easier (Python with ECMAScript 7 for instance).
PEP 492 was accepted in CPython 3.5.0 with__aiter__ defined asa method, that was expected to return an awaitable resolving to anasynchronous iterator.
In 3.5.2 (asPEP 492 was accepted on a provisional basis) the__aiter__ protocol was updated to return asynchronous iteratorsdirectly.
The motivation behind this change is to make it possible toimplement asynchronous generators in Python. See[19] and[20] formore details.
While it is possible to just implementawait expression and treatall functions with at least oneawait as coroutines, this approachmakes APIs design, code refactoring and its long time support harder.
Let’s pretend that Python only hasawait keyword:
defuseful():...awaitlog(...)...defimportant():awaituseful()
Ifuseful() function is refactored and someone removes allawait expressions from it, it would become a regular pythonfunction, and all code that depends on it, includingimportant()would be broken. To mitigate this issue a decorator similar to@asyncio.coroutine has to be introduced.
For some people bareasyncname():pass syntax might look moreappealing thanasyncdefname():pass. It is certainly easier totype. But on the other hand, it breaks the symmetry betweenasyncdef,asyncwith andasyncfor, whereasync is a modifier,stating that the statement is asynchronous. It is also more consistentwith the existing grammar.
async is an adjective, and hence it is a better choice for astatement qualifier keyword.awaitfor/with would imply thatsomething is awaiting for a completion of afor orwithstatement.
async keyword is astatement qualifier. A good analogy to it are“static”, “public”, “unsafe” keywords from other languages. “asyncfor” is an asynchronous “for” statement, “async with” is anasynchronous “with” statement, “async def” is an asynchronous function.
Having “async” after the main statement keyword might introduce someconfusion, like “for async item in iterator” can be read as “for eachasynchronous item in iterator”.
Havingasync keyword beforedef,with andfor alsomakes the language grammar simpler. And “async def” better separatescoroutines from regular functions visually.
Transition Plan section explains how tokenizer is modified to treatasync andawait as keywordsonly inasyncdef blocks.Henceasyncdef fills the role that a module level compilerdeclaration likefrom__future__importasync_await would otherwisefill.
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__anext__becomes__async_next__. However, to align new magic methods withthe existing ones, such as__radd__ and__iadd__ it was decidedto use a shorter version.
An alternative idea about new asynchronous iterators and contextmanagers was to reuse existing magic methods, by adding anasynckeyword to their declarations:
classCM:asyncdef__enter__(self):# instead of __aenter__...
This approach has the following downsides:
with andasyncwith statements;__enter__ and/or__exit__ in Python <= 3.4;The vision behind existing generator-based coroutines and this proposalis to make it easy for users to see where the code might be suspended.Making existing “for” and “with” statements to recognize asynchronousiterators and context managers will inevitably create implicit suspendpoints, making it harder to reason about the code.
Syntax for asynchronous comprehensions could be provided, but thisconstruct is outside of the scope of this PEP.
Syntax for asynchronous lambda functions could be provided, but thisconstruct is outside of the scope of this PEP.
This proposal introduces no observable performance impact. Here is anoutput of python’s official set of benchmarks[4]:
python perf.py -r -b default ../cpython/python.exe ../cpython-aw/python.exe[skipped]Report on Darwin ysmac 14.3.0 Darwin Kernel Version 14.3.0:Mon Mar 23 11:59:05 PDT 2015; root:xnu-2782.20.48~5/RELEASE_X86_64x86_64 i386Total CPU cores: 8### etree_iterparse ###Min: 0.365359 -> 0.349168: 1.05x fasterAvg: 0.396924 -> 0.379735: 1.05x fasterSignificant (t=9.71)Stddev: 0.01225 -> 0.01277: 1.0423x largerThe following not significant results are hidden, use -v to show them:django_v2, 2to3, etree_generate, etree_parse, etree_process, fastpickle,fastunpickle, json_dump_v2, json_load, nbody, regex_v8, tornado_http.
There is no observable slowdown of parsing python files with themodified tokenizer: parsing of one 12Mb file(Lib/test/test_binop.py repeated 1000 times) takes the same amountof time.
The following micro-benchmark was used to determine performancedifference between “async” functions and generators:
importsysimporttimedefbinary(n):ifn<=0:return1l=yield frombinary(n-1)r=yield frombinary(n-1)returnl+1+rasyncdefabinary(n):ifn<=0:return1l=awaitabinary(n-1)r=awaitabinary(n-1)returnl+1+rdeftimeit(func,depth,repeat):t0=time.time()for_inrange(repeat):o=func(depth)try:whileTrue:o.send(None)exceptStopIteration:passt1=time.time()print('{}({}) *{}: total{:.3f}s'.format(func.__name__,depth,repeat,t1-t0))
The result is that there is no observable performance difference:
binary(19)*30:total53.321sabinary(19)*30:total55.073sbinary(19)*30:total53.361sabinary(19)*30:total51.360sbinary(19)*30:total49.438sabinary(19)*30:total51.047s
Note that depth of 19 means 1,048,575 calls.
The reference implementation can be found here:[3].
asyncdef and newawaitkeyword.__await__ method for Future-like objects, and newtp_as_async.am_await slot inPyTypeObject.asyncwith. Andassociated protocol with__aenter__ and__aexit__ methods.asyncfor. Andassociated protocol with__aiter__,__aexit__ and new built-inexceptionStopAsyncIteration. Newtp_as_async.am_aiterandtp_as_async.am_anext slots inPyTypeObject.AsyncFunctionDef,AsyncFor,AsyncWith,Await.sys.set_coroutine_wrapper(callback),sys.get_coroutine_wrapper(),types.coroutine(gen),inspect.iscoroutinefunction(func),inspect.iscoroutine(obj),inspect.isawaitable(obj),inspect.getcoroutinestate(coro),andinspect.getcoroutinelocals(coro).CO_COROUTINE andCO_ITERABLE_COROUTINE bit flags for codeobjects.collections.abc.Awaitable,collections.abc.Coroutine,collections.abc.AsyncIterable, andcollections.abc.AsyncIterator.PyCoro_Type (exposed to Python astypes.CoroutineType) andPyCoroObject.PyCoro_CheckExact(*o) to test ifo is anative coroutine.While the list of changes and new things is not short, it is importantto understand, that most users will not use these features directly.It is intended to be used in frameworks and libraries to provide userswith convenient to use and unambiguous APIs withasyncdef,await,asyncfor andasyncwith syntax.
All concepts proposed in this PEP are implemented[3] and can betested.
importasyncioasyncdefecho_server():print('Serving on localhost:8000')awaitasyncio.start_server(handle_connection,'localhost',8000)asyncdefhandle_connection(reader,writer):print('New connection...')whileTrue:data=awaitreader.read(8192)ifnotdata:breakprint('Sending{:.10}... back'.format(repr(data)))writer.write(data)loop=asyncio.get_event_loop()loop.run_until_complete(echo_server())try:loop.run_forever()finally:loop.close()
The implementation is tracked in issue 24017[15]. It wascommitted on May 11, 2015.
I thank Guido van Rossum, Victor Stinner, Elvis Pranskevichus, AndrewSvetlov, Łukasz Langa, Greg Ewing, Stephen J. Turnbull, Jim J. Jewett,Brett Cannon, Alyssa Coghlan, Steven D’Aprano, Paul Moore, NathanielSmith, Ethan Furman, Stefan Behnel, Paul Sokolovsky, Victor Petrovykh,and many others for their feedback, ideas, edits, criticism, codereviews, and discussions around this PEP.
This document has been placed in the public domain.
Source:https://github.com/python/peps/blob/main/peps/pep-0492.rst
Last modified:2025-02-01 08:55:40 GMT