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 יוצא כרגיל. עבור מקורות שלעולם אינם מסתיימים – זרם חיישן רציף, מנוי ארוך-טווח – הלולאה רצה עד שהקורוטינה שבה היא חיה מבוטלת או שהמשימה שלה מפורקת.