גנרטור generator
גנרטור הוא סוג מסוים של איטרטור – מאפשר יצירה מהירה ונוחה של איטרטורים - (אבל לא כל איטרטור הוא גנרטור) – הדבר נעשה באמצעות הביטוי ,yield המניב בכל פעם איבר נוסף בסדרה ועושה זאת לפי דרישה בלבד (וזוכר רק את האיבר האחרון שהוא הניב ולא שומר את כל הסדרה) – הגנרטור שנראה בדוגמא הבאה יודע, לפני שהוא מנוצל ונשרף (consumed), לייצר לנו למשל את ריבועי המספרים, מנקודת התחלה שנבחר ועד לנקודת סיום שנבחר (לא כולל את נקודת הסיום). למטה אנו בונים את הפונקציה squarit שמחזירה ערכים לפי דרישה (ולא מייצרת מראש רשימה שלהם) באמצעות הפקודה yield (להניב)-
def squarit(start, stop): for i in range(start, stop): yield i * i generator = squarit(1, 6) print(next(generator)) print(next(generator)) print(next(generator)) print(next(generator))
>>>
1
4
9
16
המשתנה generator מימוש של הפונקציה בתחום הערכים 1 עד 6 (לא כולל 6) ובכל פעם יניב את הערך הריבועי הבא בתור. כשיסיים את עבודתו, אם נמשיך לבקש את האיבר הבא, נקבל הודעת שגיאה.
אנו יכולים לקבל את כל האיברים שמייצר הגנרטור באמצעות לולאה for x in generator ולערוך איתם חישובים או חיפושים כפי רצוננו, או סתם להדפיס את כולם.
ניתן גם לייצר טיפוס מסוג גנרטור באופן דומה ליצירת list comprehension רק עם סוגריים עגולים – נראה איך זה עובד –
gen=(x**2 for x in range(5)) print(type(gen)) >>> <class 'generator'> print(next(gen)) >>> 0 print(next(gen)) >>> 1 print(next(gen)) >>> 4 print(next(gen)) >>> 9
למעלה יצרנו גנרטור, כאשר אנו מבקשים להדפיס את סוג הטיפוס, אנו מקבלים את התשובה generator מה שמחזק את התחושה הפנימית שזה באמת גנרטור. הגנרטור חסכוני בזכרון, ומניב רק את האיבר שצריך כל פעם, בלי לשמור רשימות ארוכות בזכרון. זה גם מאיץ את מהירות עבודת התוכנה, ויש שלבים, אפילו אצל מתחילים, שהחישובים המרובים מתחילים להיות ארוכים בזמן, לפעמים רק בבוקר למחרת אפשר לראות אם התוכנית פתרה חידה מורכבת מסוימת. לכן, שימוש באיטרטורים בכלל וגנרטורים בפרט הוא קריטי.
כדי להשוות זמני עבודה של איטרטורים אל מול list comprehension אפשר להשתמש במודול time המסוגל למדוד זמנים בחלקיקי שנייה בין תהליך אחד לאחר – נערוך השוואה כזאת, וגם נסגור את הפינה לגבי אחד השימושים הנחמדים במודל time -
import time time0=time.time() lista=[x for x in range(10000000)] for i in lista: pass time1=time.time() gen=(x for x in range(10000000)) for i in gen: pass time2=time.time() print(time2-time1,time1-time0)
>>>
0.799553394317627 1.0324063301086426
למעלה אנו רואים איך אפשר למדוד זמנים בין תהליך לתהליך, בכל פעם מוסיפים משתנה עם הזמן הנוכחי ()time.time ולבסוף מודדים את ההפרשים ביניהם. אנו רואים שגנרטורים מהירים ביותר מ- 20% 0.8 שניות במקום 1 שנייה שלימה לתהליך שהרצנו למעלה. אבל בממוצע, גנרטורים יהיו הרבה יותר מהירים משום שאם אנו מחפשים איבר מסוים בתוך סדרה, ובממוצע הוא ימצא במקום טוב באמצע, לגנרטור אין צורך לייצר את כל איברי הסדרה ולאחסן אותם בזיכרון ורק אז להתחיל לחפש, בעוד שברשימה, לא משנה היכן ממוקם האיבר שאנו מחפשים, פייתון מייצרת רשימה המלאה בכל האיברים לפני שהיא מתחילה לחפש, ובכך אף מעמיסה על הזיכרון מעבר לזמן ריצה של התוכנית שיכול להיות משמעותית קצר יותר בגנרטור.
אם הזכרנו את המודול time, נזכיר גם את אח שלו, המודול datetime שמחזיר תאריכים ומועדים בפורמט יותר קריא, מאשר המודול time המחזיר מספר עשרוני המייצג שניות משנת 1970 באופן רציף עד היום. להלן סרטון הדרכה שלנו בנושא גנרטורים המדגים גם את יעילות הגנרטור מול רשימה: