Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why am I getting different results when using a list comprehension with coroutines with asyncio?

I initially had some code that aggregated results into a list. When I refactored this code to use a list comphrehension, I am getting unexpected results:

import asyncio

@asyncio.coroutine
def coro():
    return "foo"


# Writing the code without a list comp works,
# even with an asyncio.sleep(0.1).
@asyncio.coroutine
def good():
    yield from asyncio.sleep(0.1)
    result = []
    for i in range(3):
        current = yield from coro()
        result.append(current)
    return result


# Using a list comp without an async.sleep(0.1)
# works.
@asyncio.coroutine
def still_good():
    return [(yield from coro()) for i in range(3)]


# Using a list comp along with an asyncio.sleep(0.1)
# does _not_ work.
@asyncio.coroutine
def huh():
    yield from asyncio.sleep(0.1)
    return [(yield from coro()) for i in range(3)]


loop = asyncio.get_event_loop()
print(loop.run_until_complete(good()))
print(loop.run_until_complete(still_good()))
print(loop.run_until_complete(huh()))

If I run this code I get this output:

$ python3.4 /tmp/test.py
['foo', 'foo', 'foo']
['foo', 'foo', 'foo']
<generator object <listcomp> at 0x104eb1360>

Why do I get different results for third huh() function?

like image 325
jamesls Avatar asked Mar 29 '15 19:03

jamesls


People also ask

How do python coroutines work?

Coroutines work cooperatively multitask by suspending and resuming at set points by the programmer. In Python, coroutines are similar to generators but with few extra methods and slight changes in how we use yield statements. Generators produce data for iteration while coroutines can also consume data.

Is Python async concurrent?

The intended use of asyncio tasks is to allow independently running tasks to run 'concurrently' with other tasks within the same event loop.

How does asyncio work in Python?

asyncio is a library to write concurrent code using the async/await syntax. 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.

How do I get rid of coroutines in Python?

To cancel a running Task use the cancel() method. Calling it will cause the Task to throw a CancelledError exception into the wrapped coroutine. If a coroutine is awaiting on a Future object during cancellation, the Future object will be cancelled. cancelled() can be used to check if the Task was cancelled.


1 Answers

A fix to your problem would be to put next(...) instead of ... in the return of the third function, or better write return list((yield from coro()) for i in range(3)) (credits to @zch for this idea), or even better stay with the first function.


The point is that the second function is not a generator. It is just an ordinary function that returns a comprehension generator.

For example this code is valid outside generator:

values = [(yield x) for x in range(3)]

Then you can do this:

next(values)
0
next(values)
1
next(values)
2
next(values)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration: [None, None, None]

Decorator @coroutine then makes the second function a generator by iterating over the result, see here, line 143.

On the contrast, the first and the third functions are actually generators, and the @coroutine decorator just return themselves, see here, lines 136-137. In first case the generator returns list (actualy raises StopIteration(['foo', 'foo', 'foo'])). In the third case it returns the comprehension generator.

like image 168
ivanl Avatar answered Oct 10 '22 18:10

ivanl