53
votes

I want to use generator yield and async functions. I read this topic, and wrote next code:

import asyncio

async def createGenerator():
    mylist = range(3)
    for i in mylist:
        await asyncio.sleep(1)
        yield i*i

async def start():
    mygenerator = await createGenerator()
    for i in mygenerator:
        print(i)

loop = asyncio.get_event_loop()

try:
    loop.run_until_complete(start())

except KeyboardInterrupt:
    loop.stop()
    pass

But i got the error:

SyntaxError: 'yield' inside async function

How to use yield generator in async function?

3
Is that possible? It seems like two opposing designs. Generators are made not to produce value unless needed, it means they in principle need to handle having a state. async on the other hand would suggest that the called function cannot depend on it state. Otherwise you will have data races. It seems really cumbersome to support async generators, they would need to be wrapped with locking mechanisms. So probably answer to your question is somewhere in this direction. - luk32
can you return a Future object and then yield that object when you want it's data. I've never used asyncio, but that's how it's done with Tornado. - reticentroot
I don't think that an asyncrounus generator makes any sense. You should be able to return a generator from an async function. Is there something you want to achieve or are you just trying things out? - syntonym
May be use Event? createGenerator will be set event, and start will be wait event. I wrote this solution. It's work, but i want more nice code. - Ильдар
@Ильдар did you see second answer? What you think about it? Looks like it works. - Mikhail Gerasimov

3 Answers

72
votes

Upd:

Starting with Python 3.6 we have asynchronous generators and able to use yield directly inside coroutines.

import asyncio


async def async_generator():
    for i in range(3):
        await asyncio.sleep(1)
        yield i*i


async def main():
    async for i in async_generator():
        print(i)


loop = asyncio.get_event_loop()
try:
    loop.run_until_complete(main())
finally:
    loop.run_until_complete(loop.shutdown_asyncgens())  # see: https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.shutdown_asyncgens
    loop.close()

Old answer for Python 3.5:

You can't yield inside coroutines. Only way is to implement Asynchronous Iterator manually using __aiter__/__anext__ magic methods. In your case:

import asyncio


class async_generator:
    def __init__(self, stop):
        self.i = 0
        self.stop = stop

    async def __aiter__(self):
        return self

    async def __anext__(self):
        i = self.i
        self.i += 1
        if self.i <= self.stop:
            await asyncio.sleep(1)
            return i * i
        else:
            raise StopAsyncIteration


async def main():
    async for i in async_generator(3):
        print(i)


if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

Output:

0
1
4

Here're two more examples: 1, 2

7
votes

New Python 3.6 comes with support for asynchronous generators.

PEP 0525

What's new in Python 3.6

PS: On the moment of writing Python 3.6 is still beta. If you are on GNU/Linux or OS X and you cannot wait you can try new Python with pyenv.

3
votes

This should work with python 3.6 (tested with 3.6.0b1):

import asyncio

async def createGenerator():
    mylist = range(3)
    for i in mylist:
        await asyncio.sleep(1)
        yield i*i

async def start():
    async for i in createGenerator():
        print(i)

loop = asyncio.get_event_loop()

try:
    loop.run_until_complete(start())

except KeyboardInterrupt:
    loop.stop()
    pass