Iterating over asyncio.coroutine

python, python-3.4, python-asyncio

Solution

In asyncio coroutines you should to use `yield from` and never `yield`. That's by design. Argument for `yield from` should be another coroutine or `asyncio.Future` instance only.

Calls of coroutine itself should be used with `yield from` again like `yield from countdown(5)`.

For your case I recommend using queues:

import asyncio

@asyncio.coroutine
def countdown(n, queue):
    while n > 0:
        yield from asyncio.sleep(1)
        n = n - 1
        yield from queue.put(n)
    yield from queue.put(None)

@asyncio.coroutine
def do_work():
    queue = asyncio.Queue()
    asyncio.async(countdown(5, queue))
    while True:
        v = yield from queue.get()
        if v:
            print(v)
        else:
            break

asyncio.get_event_loop().run_until_complete(do_work())

Well, you can use check for values yielded by `countdown`, the following example works. But I think it is antipattern:

Too easy to make a mess

You anyway cannot compose `countdown` calls with, say, `itertools` functions. I mean something like `sum(countdown(5))` or `itertools.accumulate(countdown(5))`.

Anyway, example with mixing `yield` and `yield from` in coroutine:

import asyncio

@asyncio.coroutine
def countdown(n):
    while n > 0:
        yield from asyncio.sleep(1)
        n = n - 1
        yield n

@asyncio.coroutine
def do_work():
    for n in countdown(5):
        if isinstance(n, asyncio.Future):
            yield from n
        else:
            print(n)

asyncio.get_event_loop().run_until_complete(do_work())

Problem

I've been mucking around with asyncio recently, and while I'm beginning to get an intuition for how it works, there's something that I've not been able to do. I'm not sure if it's because I've got the construction wrong, or if there's a reason why what I'm trying to do doesn't make sense. In short, I want to be able to iterate over a yielding asyncio.coroutine. For example, I'd like to be able to do something like: ``` @asyncio.coroutine def countdown(n): while n > 0: yield from asyncio.sleep(1) n = n - 1 yield n @asyncio.coroutine def do_work(): for n in countdown(5): print(n) loop.run_until_complete(do_work()) ``` However, this throws an exception from the bowels of asyncio. I've tried other things, like `for n in (yield from countdown(5)): ...` but that also gives a similarly opaque runtime exception. I can't immediately see why you shouldn't be do something like this, but I'm getting to the limits of my ability to understand what's going on. So: - if it is possible to do this, how can I do it? - if it is not possible, why not? Let me know if this question's not clear!

Original source