The best source of information about asynchronous generators is the PEP that describes their need, and their implementation - the PEP 525 .
To briefly describe asynchronous generators, let's briefly recap the use of asynchronous functions (but not their internal mechanism).
Asynchronous code in Python and other languages is characterized by code capable of running on a single thread, but in which the programmer can put explicit points where the code of a function (generator, method, etc.) ..) can be paused while waiting for a result. During this pause, a "controller" of the execution of the asynchronous code passes the execution to other asynchronous functions paused in the same way, check if results have been expected, etc ....
In Python this pause is characterized by the keywords await
and async
- whenever one of them appears in the body of a function, the code ' should be running in an asynchronous context. / p>
It turns out that in Python 3.5 the command async for
- that is, a for
has already been defined, where at each inertia the program control is passed to the event loop until the next element of the iterator is available. The iterator to be used in a async for
in Python 3.5 would have to define the special methods __aiter__
and __anext__
as co-routines (asynchronous functions, declared with async def
) - in contrast to __iter__
and __next__
of normal iterators.
But for a normal%, non-asynchronous%, any function that contains the expression for
in your body is automatically converted to yield
: that is, an object that has methods generator
and __iter__
and can be used in __next__
. A for
, before Python 3.6 and PEP 525, could only iterate over a class defined with the asynchronous versions of these methods, and attempting to use the expression async for
in the body of an asynchronous function resulted in a syntax error :
Python 3.5.5 (default, Jun 8 2018, 09:55:12)
...
>>> async def a():
... yield 1
...
File "<stdin>", line 2
SyntaxError: 'yield' inside async function
Only in Python 3.6 can you directly declare an async generator:
Python 3.6.5 (default, Jun 8 2018, 09:56:48)
...
>>> async def a():
... yield 1
...
>>>
And it is usable, in another asynchronous function, always managed by the event loop, by the command yield
:
import asyncio
loop = asyncio.get_event_loop()
import random
async def random_pause_generator(start, stop):
for i in range(start, stop):
await asyncio.sleep(random.random())
yield i
async def counter(start, stop):
async for j in random_pause_generator(start, stop):
print(j)
async def main():
await asyncio.gather(counter(0, 10), counter(110, 120))
loop.run_until_complete(main())
Execute this code and notice how the two calls to the "counter" routine are executed in parallel, with results of each call appearing out of order. It is interesting to note the async for
call that unifies the two calls waiting in a "single hold". If you simply use asyncio.gather
, the end result is the same as in a synchronous function: the program would only continue on the bottom line after the await counter(10, 20)
call resolves itself completely.
Extra note about async: The controller code in Python is simply referred to by the "event loop", and is a special object that, in case of use of the asyncio library can always be retrieved by the counter
call. (Note that it is possible to have other library implementations to coordinate the execution of asynchronous code, and they may have distinct calls to retrieve the event loop - but since asyncio is flexible enough to allow the use of other classes as event loop, this would be just reinvent the wheel)