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 ממתינה (await) לכל קריאה ל-__anext__, כך שהקורוטינה שמריצה את הלולאה מוסרת שליטה ללולאת האירועים בין ערכים בדיוק כמו כל await אחר.

8.11.2. מתי להשתמש בזה

בכל פעם שלאפליקציה יש מקור נתונים שמגיע לאורך זמן והצרכן צריך לעבור עליו באופן עצל. חיישן שמייצר דגימה אחת לכל פרק זמן, שקע רשת שמספק הודעות, מחולל פריימים – בכל מקום שבו הצורה while True: x = await source.next(); process(x) מתאימה, async for x in source היא הכתיבה הנקייה יותר.

עבור מקורות שפולטים נתונים עד לתנאי סיום סופי, האיטרטור מעלה StopAsyncIteration וה-async for יוצא כרגיל. עבור מקורות שלעולם אינם מסתיימים – זרם חיישן רציף, מנוי ארוך-טווח – הלולאה רצה עד שהקורוטינה שבה היא חיה מבוטלת או שהמשימה שלה מפורקת.