Ленивые итераторы (генераторы) с asyncio

У меня есть блокирующий, не асинхронный код, подобный этому:

def f():
    def inner():
        while True:
            yield read()
    return inner()

С помощью этого кода вызывающая сторона может выбрать, когда остановить функцию для генерации данных. Как изменить это на асинхронный? Это решение не работает:

async def f():
    async def inner():
        while True:
            yield await coroutine_read()
    return inner()

... так как yield не может быть использован в async def функции. Если я удалю async от inner() подпись, я не могу использовать await больше.

1 ответ

Решение

Как отмечено выше, вы не можете использовать yield внутри async funcs. Если вы хотите создать генератор сопрограмм, вы должны сделать это вручную, используя __aiter__ а также __anext__ магические методы:

import asyncio


# `coroutine_read()` generates some data:
i = 0
async def coroutine_read():
    global i
    i += 1
    await asyncio.sleep(i)
    return i


# `f()` is asynchronous iterator.
# Since we don't raise `StopAsyncIteration` 
# it works "like" `while True`, until we manually break.
class f:
    async def __aiter__(self):
        return self

    async def __anext__(self):
        return await coroutine_read()


# Use f() as asynchronous iterator with `async for`:
async def main():
    async for i in f():
        print(i)
        if i >= 3:
            break


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

Выход:

1
2
3
[Finished in 6.2s]

Вам также может понравиться другой пост, где StopAsyncIteration использует.

Upd:

Начиная с Python 3.6 у нас есть асинхронные генераторы и возможность использовать yield прямо внутри сопрограмм.

Другие вопросы по тегам