Для чего нужен async for?

Я не понимаю, для чего нужен async for. Пожалуйста, подробно объясните что это такое и как им пользоваться. Приведите простой пример и желательно ещё пример реального использования.


Ответы (1 шт):

Автор решения: insolor

Очень коротко:

  • Обычный for нужен для итерирования по обычным (синхронным) итераторам и итерируемым объектам
  • асинхронный for нужен для итерирования по асинхронным итераторам и итерируемым объектам

Это в общем-то и вся разница. По крайней мере, поверхностная разница, к которой прилагается нижняя часть "айсберга" асинхронного программироваия.

Если какая-то библиотека асинхронная (или поддерживает асинхронность), она может возвращать где-то асинхронный итератор/итерируемый объект с набором результатов. Как про это узнать - посмотреть документацию. Вот пример с async for из документации Django 4.1:

async for author in Author.objects.filter(name__startswith="A"):
    book = await author.books.afirst()

В своем коде можно реализовать свои асинхронные итераторы или итерирумые объекты.

  • В обычных итераторах нужно реализовать методы __iter__ (у итераторов __iter__ возвращает сам объект, т.е. self) и __next__, __next__ выбрасывает исключение StopIteration при исчерпании итератора. В итерируемых объектах - просто __iter__, который должен возвращать итератор.

  • В асинхронных итераторах и итерируемых объектах аналогично, но методы должны быть def __aiter__ и async def __anext__, и __anext__ должен бросать StopAsyncIteration при исчерпании.

Синхронный пример:

import time
import random


class Iterator:
    def __init__(self, n):
        self.i = n

    def __iter__(self):
        return self
    
    def __next__(self):
        if self.i <= 0:
            raise StopIteration
        
        self.i -= 1
        
        time.sleep(0.5)
        return random.randint(0, 100)



def main():
    for x in Iterator(10):
        print(x)


main()

Асинхронный пример:

import asyncio
import random


class AsyncIterator:
    def __init__(self, n):
        self.i = n

    def __aiter__(self):
        return self
    
    async def __anext__(self):
        if self.i <= 0:
            raise StopAsyncIteration
        
        self.i -= 1
        
        await asyncio.sleep(0.5)
        return random.randint(0, 100)



async def main():
    async for x in AsyncIterator(10):
        print(x)


asyncio.run(main())

Также есть асинхроные генераторы (функции, возвращающие значения через yield), в общем-то единственное синтаксическое отличие от обычных - наличие async перед def:

import asyncio
import random


async def async_generator(n):
    for _ in range(n):
        yield random.randint(0, 100)
        await asyncio.sleep(0.5)


async def main():
    async for x in async_generator(10):
        print(x)


asyncio.run(main())

Ну и выражения-генераторы с async for нужны чтобы собрать результат в список или другой контейнер:

async def main():
    result = [x async for x in async_generator(10)]
    print(result)
→ Ссылка