2.31. איטרטורים וגנרטורים¶
לולאת ה-for עשתה יותר עבודה ממה שזה נראה. עמוד זה מכסה את פרוטוקול האיטרטור שעליו היא פועלת, ואת מילת המפתח yield המאפשרת לך לבנות איטרטורים משלך.
2.31.1. פרוטוקול האיטרטור¶
כל אובייקט שניתן לבצע עליו לולאה ממש שתי מתודות:
__iter__()– מחזירה איטרטור על פריטי האובייקט.__next__()– על האיטרטור, מחזירה את הפריט הבא או מעלהStopIterationכאשר אין עוד.
ה-built-in iter() קורא ל-__iter__; next() קורא ל-__next__. צעד דרך רשימה ידנית:
it = iter([10, 20, 30])
print(next(it)) # 10
print(next(it)) # 20
print(next(it)) # 30
print(next(it)) # raises StopIteration
for הוא קיצור עבור ”קריאה ל-__iter__ פעם אחת, ואז לולאה על __next__ עד StopIteration.“¶
מה ש-for x in items: עושה למעשה:
_it = iter(items)
while True:
try:
x = next(_it)
except StopIteration:
break
# ... loop body ...
כל רשימה, tuple, מחרוזת, dict, set, אובייקט קובץ וגנרטור כבר ממשים __iter__ ו-__next__ – ולכן כולם עובדים עם for.
2.31.2. yield ופונקציות גנרטור¶
פונקציה המכילה הצהרת yield היא פונקציית גנרטור. קריאה לה אינה מריצה את הגוף; היא מחזירה אובייקט גנרטור (איטרטור) שמריץ את הגוף yield אחד בכל פעם:
def count_up_to(n):
i = 0
while i < n:
yield i
i += 1
for value in count_up_to(3):
print(value)
פלט:
0
1
2
כל קריאה ל-next() מחדשת את הפונקציה עד ה-yield הבא, מעבירה את הערך הזה לקורא, ועוצרת שם. המצב המקומי (i במקרה זה) נשמר בין החידושים.
next() מריץ את הגוף עד ה-yield הבא, מחזיר את הערך, ועוצר. המצב המקומי שורד את העצירה.¶
גנרטורים הם הדרך הקלה ביותר לייצר רצף בעצלתיים – לא נבנית רשימה, פריטים מחושבים רק כאשר הצרכן מבקש אותם, והפונקציה יכולה להניב פריטים לנצח אם תרצה.
2.31.3. צינורות עצלים¶
גנרטורים מתחברים היטב. הפלט של גנרטור אחד יכול להזין אחר:
def numbers():
i = 0
while True:
yield i
i += 1
def squares(source):
for x in source:
yield x * x
pipeline = squares(numbers())
for v in pipeline:
if v > 100:
break
print(v)
הערכים זורמים דרך הצינור אחד בכל פעם – ללא רשימת ביניים, ללא חסם עליון מובנה ב-numbers, והצרכן (for v in pipeline) מחליט מתי לעצור.
כל next() על הצרכן מפעיל משיכה אחת דרך השרשרת; ערכים קיימים רק כאשר משהו מבקש אותם.¶
2.31.3.1. yield from¶
לולאה שמושכת פריטים מ-iterable אחר ומניבה כל אחד היא נפוצה מספיק כך ש-Python מספק קיצור. הביטוי yield from iter מניב כל ערך שה-iterable מייצר, לפי הסדר – כאילו לגנרטור הייתה לולאת for x in iter: yield x בתוכו:
def chain(*sources):
for source in sources:
yield from source
for v in chain([1, 2, 3], (4, 5), "abc"):
print(v)
פלט:
1
2
3
4
5
a
b
c
yield from שקול בדיוק ללולאת ה-for המפורשת, רק קצר יותר, והוא מעביר StopIteration מה-iterable הפנימי לגנרטור החיצוני בנקיות – שימושי בעת שרשור של כמה גנרטורים קצה-לקצה.
2.31.3.2. כאשר yield נגמר¶
נפילה אל מעבר לסוף של פונקציית גנרטור (או פגיעה ב-return מפורש) מעלה StopIteration אוטומטית. אין צורך להעלות אותה ידנית; לולאת ה-for המקיפה רואה אותה ומסתיימת.
השתמש בגנרטורים כאשר הקוד המייצר נכתב באופן טבעי כלולאה עם מספר נקודות yield; השתמש בהבנת רשימה (list comprehension) פשוטה כאשר אתה באמת זקוק לכל הרצף בזיכרון.