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) פשוטה כאשר אתה באמת זקוק לכל הרצף בזיכרון.