8.11. 非同期イテレータ

非同期イテレータ は、イテレータの asyncio 版です。Python の概要 では、同期イテレータプロトコル(__iter____next__)と、それを駆動する for ループを紹介しました。非同期イテレータは各ステップに await を追加するため、イテレータはイベントループをブロックすることなく値の間で 待機 できます。

8.11.1. プロトコル

クラスが次を実装している場合、そのクラスは非同期イテレータになります

  • __aiter__(self) -- イテレータオブジェクト(通常は self)を返します。

  • async def __anext__(self) -- 次の値を返すか、これ以上値がない場合は StopAsyncIteration を送出します。

対応するループ構文は async for です:

import asyncio


class FrameCounter:
    """Yield N integers, one per frame period."""

    def __init__(self, n, period_ms):
        self._n = n
        self._period_ms = period_ms
        self._i = 0

    def __aiter__(self):
        return self

    async def __anext__(self):
        if self._i >= self._n:
            raise StopAsyncIteration
        await asyncio.sleep_ms(self._period_ms)
        self._i += 1
        return self._i

async def main():
    async for n in FrameCounter(5, 200):
        print(n)

async for ループは __anext__ への各呼び出しを await するため、ループを実行しているコルーチンは、他の await と同様に、値の間でイベントループに制御を譲ります。

8.11.2. いつ使うべきか

アプリケーションに 時間とともに 到着するデータソースがあり、コンシューマがそれを遅延的に反復処理すべき場合にはいつでも使います。期間ごとにサンプルを生成するセンサー、メッセージを配信するネットワークソケット、フレームジェネレータなど、while True: x = await source.next(); process(x) という形が当てはまる場面では、async for x in source のほうがすっきりした書き方になります。

有限の終了条件まで値を発行するソースの場合、イテレータは StopAsyncIteration を送出し、async for は正常に終了します。決して終わらないソース(連続的なセンサーストリーム、長時間稼働するサブスクリプションなど)の場合、ループはそれを含むコルーチンがキャンセルされるか、そのタスクが破棄されるまで実行され続けます。