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?
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.
The intended use of asyncio tasks is to allow independently running tasks to run 'concurrently' with other tasks within the same event loop.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With