Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python Asynchronous Comprehensions - how do they work?

I'm having trouble understanding the use of asynchronous comprehensions introduced in Python 3.6. As a disclaimer, I don't have a lot of experience dealing with asynchronous code in general in Python.

The example given in the what's new for Python 3.6 document is:

result = [i async for i in aiter() if i % 2] 

In the PEP, this is expanded to:

result = [] async for i in aiter():     if i % 2:         result.append(i) 

I think I understand that the aiter() function gets called asynchronously, so that each iteration of aiter can proceed without the previous one necessarily returning yet (or is this understanding wrong?).

What I'm not sure about is how that then translates to the list comprehension here. Do results get placed into the list in the order that they are returned? Or are there effective 'placeholders' in the final list so that each result is placed in the list in the right order? Or am I thinking about this the wrong way?

Additionally, is someone able to provide a real-world example that would illustrate both an applicable use case and the basic mechanics of async in comprehensions like this?

like image 621
Andrew Guy Avatar asked Feb 20 '17 02:02

Andrew Guy


People also ask

How does asynchronous programming work in Python?

Asynchronous programming is a type of parallel programming in which a unit of work is allowed to run separately from the primary application thread. When the work is complete, it notifies the main thread about completion or failure of the worker thread.

How does Python handle asynchronous calls?

To run an async function (coroutine) you have to call it using an Event Loop. Event Loops: You can think of Event Loop as functions to run asynchronous tasks and callbacks, perform network IO operations, and run subprocesses. Example 1: Event Loop example to run async Function to run a single async function: Python3.

How does async with work?

The async keyword turns a method into an async method, which allows you to use the await keyword in its body. When the await keyword is applied, it suspends the calling method and yields control back to its caller until the awaited task is complete. await can only be used inside an async method.

Does Python support asynchronous programming?

asyncio is used as a foundation for multiple Python asynchronous frameworks that provide high-performance network and web-servers, database connection libraries, distributed task queues, etc. asyncio is often a perfect fit for IO-bound and high-level structured network code.


2 Answers

You are basically asking how an async for loop works over a regular loop. That you can now use such a loop in a list comprehension doesn't make any difference here; that's just an optimisation that avoids repeated list.append() calls, exactly like a normal list comprehension does.

An async for loop then, simply awaits each next step of the iteration protocol, where a regular for loop would block.

To illustrate, imagine a normal for loop:

for foo in bar:     ... 

For this loop, Python essentially does this:

bar_iter = iter(bar) while True:     try:         foo = next(bar_iter)     except StopIteration:         break     ... 

The next(bar_iter) call is not asynchronous; it blocks.

Now replace for with async for, and what Python does changes to:

bar_iter = aiter(bar)  # aiter doesn't exist, but see below while True:     try:         foo = await anext(bar_iter)  # anext doesn't exist, but see below     except StopIteration:         break     ... 

In the above example aiter() and anext() are fictional functions; these are functionally exact equivalents of their iter() and next() brethren but instead of __iter__ and __next__ these use __aiter__ and __anext__. That is to say, asynchronous hooks exist for the same functionality but are distinguished from their non-async variants by the prefix a.

The await keyword there is the crucial difference, so for each iteration an async for loop yields control so other coroutines can run instead.

Again, to re-iterate, all this already was added in Python 3.5 (see PEP 492), all that is new in Python 3.6 is that you can use such a loop in a list comprehension too. And in generator expressions and set and dict comprehensions, for that matter.

Last but not least, the same set of changes also made it possible to use await <expression> in the expression section of a comprehension, so:

[await func(i) for i in someiterable] 

is now possible.

like image 138
Martijn Pieters Avatar answered Sep 20 '22 12:09

Martijn Pieters


I think I understand that the aiter() function gets called asynchronously, so that each iteration of aiter can proceed without the previous one necessarily returning yet (or is this understanding wrong?).

That understanding is wrong. Iterations of an async for loop cannot be performed in parallel. async for is just as sequential as a regular for loop.

The asynchronous part of async for is that it lets the iterator await on behalf of the coroutine iterating over it. It's only for use within asynchronous coroutines, and only for use on special asynchronous iterables. Other than that, it's mostly just like a regular for loop.

like image 35
user2357112 supports Monica Avatar answered Sep 21 '22 12:09

user2357112 supports Monica