7

I suspect this has something to do with the differences b/w yield from & await. However, & aside from the new object's designation as an async_generator, I'm unclear about consequences regarding the differences between it and a coroutine. (I'm not sure how else to ask the question other than the one I put in the title...)

import asyncio

async def async_generator_spits_out_letters():
    yield 'a'
    yield 'b'
    yield 'c'
    yield 'd'
    await asyncio.sleep(0)

async def coroutine_prints_messages():
    while True:
        print('hi')
        await asyncio.sleep(2)

def test_it():
    print(type(async_generator_spits_out_letters))
    print(type(coroutine_prints_messages))
    # This is how I choose to do newlines....it's easier for me to read. :[
    print(); print()

    print(type(async_generator_spits_out_letters()))
    print(type(coroutine_prints_messages()))

This gives:

<class 'async_generator'>
<class 'coroutine'>


<class 'function'>
<class 'function'>

I can't make heads or tails of this...

2
  • A generator yields, a coroutine does not. Commented Aug 16, 2017 at 19:45
  • Is that the only difference b/w the two? Commented Aug 16, 2017 at 20:58

1 Answer 1

9

In order for an async_generator-producing function to run in an eventloop, its output must be wrapped in a coroutine.

This is to prevent the async_generator from yielding values directly INTO the event-loop.


import asyncio

# This produces an async_generator
async def xrange(numbers):
    for i in range(numbers):
        yield i
        await asyncio.sleep(0)

# This prevents an async_generator from yielding into the loop.
async def coroutine_wrapper(async_gen, args):
    try:
        print(tuple([i async for i in async_gen(args)]))
    except ValueError:
        print(tuple([(i, j) async for i, j in async_gen(args)]))

Loops only like tasks & futures.

If a loop receives an integer or string or..anything not derived from a future from one of its tasks, it will break.

Therefore coroutines must either:

  • produce futures (or subclasses of future,)
  • OR NOT pass any values back into the loop.

Here's main():

def main():
    print('BEGIN LOOP:')
    print()
    loop = asyncio.get_event_loop()
    xrange_iterator_task = loop.create_task(coroutine_wrapper(xrange, 20))
    try:
        loop.run_until_complete(xrange_iterator_task)
    except KeyboardInterrupt:
        loop.stop()
    finally:
        loop.close()
    print()
    print('END LOOP')
    print(); print()
    print('type(xrange) == {}'.format(type(xrange)))
    print('type(xrange(20) == {}'.format(type(xrange(20))))
    print()
    print('type(coroutine_wrapper) == {}'.format(type(coroutine_wrapper)))
    print('type(coroutine_wrapper(xrange,20)) == {}'.format(type(coroutine_wrapper(xrange, 20))))
if __name__ == '__main__':
    main()

Here's the output:

BEGIN LOOP:

(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19)

END LOOP


type(xrange) == <class 'function'>
type(xrange(20)) == <class 'async_generator'>

type(coroutine_wrapper) == <class 'function'>
type(coroutine_wrapper(xrange,20)) == <class 'coroutine'>
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.