בפוסט זה נדבר על המרכיב הסודי בכל מתכון מנצח של מפתח פייתון מתקדם - ה-Generator.
נסביר במה הוא נבדל מבן דודו ה-Iterator ולמה כדאי להכיר אותו ונסיים במימוש פשוט להמחשה.
פה מתחילה הנקודה בה מפתחים שמנוסים משפות אחרות מתחילים לגרד את הראש. עד כאן, הכל היה דומה ומוכר: משתנים, לולאות, פונקציות, אובייקטים .. מה זה הגנרטור הזה ?
ובכן, אכן מדובר בחיה ייחודית יחסית בשפות התכנות (למיטב ידיעתי) והוא אינו מזכיר אלמנטים דומים משפות אחרות.
הוא לא כל כך טריביאלי להטמעה והוא מעט שובר אינטואיציה תכנותית (אם כי פחות מרקורסיה), אך בסופו של דבר מי שלומד אותו מרוויח.
נתחיל מ-למה ?
המטרה העיקרית שלו היא Performance. נכון שנושא זה הוא לא הנושא הבולט כשלומדים פייתון, אבל בכל זאת, לפייתון שימושים לא מעטים ובחלקם הוא נדרש לצמצם את זמן הריצה שלו למינימום.
נמשיך ל-איך ?
בעיקרון, אם הבנתם את העיקרון של Iterator, תוכלו להשתמש ב-Generator באותה דרך. רצים עליו כאילו הוא לולאה והוא מספק (ויזואלית) את אותן תוצאות (דוגמאות בסוף).
למי מביניכם שלא מעודכנים ומשתמשים ב-Python2 עדיין - שם תמצאו משהו שנקרא xrange שבאורח פלא נעלם ב-Python3. הסיבה - range החליף אותו.
מה range עושה ? אתם אמורים לדעת מידע בסיסי זה, אבל נסביר בכל זאת בקצרה שהוא מייצר List בהתאם לשלשה ארגומנטים: start, end, step כאשר מביניהם רק end הכרחי.
לדוגמא: range(3) יתחלף בזמן ריצה ל-[0,1,2] כאשר כפי שהבנתם, הרשימה תתחיל מ-0 ותעלה ב-1 בכל פעם כאשר הספירה לא כוללת את הספרה האחרונה.
range(1, 5) יתחלף ל-[1,2,3,4]
range(2, 10, 2) יתחלף ל-[2,4,6,8]
range(5, 1, -1) יתחלף ל-[5,4,3,2]
וכן הלאה.
אז מה הבעיה, אתם שואלים ?
תנסו לחשוב מה יקרה אם אריץ את range(10**8):
ה-Interpreter ייצור list בזיכרון בגודל עצום ורק לאחר מכן, יבצע את המשך הקוד (לולאה לרוב). אולי זה נשמע לכם הגיוני כי אתם מניחים שהקוד צריך באמת את כל הנתונים ב-list אבל מה אם אני רוצה רק לקרוא נתון אחד בכל פעם (נניח בתוך פונקציה) ואני מבצע את הבקשה הזו מיליון פעמים ? לא ממש יעיל..
אז מה עושים ? כאן ה-Generator נכנס לעבודה - מה שהוא למעשה מבצע נקרא בסלנג הפיתוחי Lazy Load שזה אומר במילים פשוטות שהוא לא "פותח" את כל ה-list בזיכרון ורק לאחר מכן ממשיך ריצה אלא "מייצר" (מכאן השם) רק את המספר הנדרש לאותה איטרציה וממשיך את האיטרציה הבאה בלולאה. תחשבו איזו השפעה זה יוצר על הביצועים !
איך הנס הזה קורה באמת ? אני יכול להסביר איך זה מתרחש ברמת הפייתון, מי שרוצה מוזמן לחקור בעצמו איך זה מתבצע מאחורי הקלעים ברמת הקומפילציה ב-C. (פייתון נכתב ב-C אם לא ידעתם)
באופן כללי ניתן לומר שכאשר ה-Interpreter של פייתון רואה את המילה yield בתוך פונקציה, הוא מתייחס אליה אוטומטית בתור Generator. המילה yield שקולה ל-return רק שבניגוד אליה, היא לא מסיימת את ריצת הפונקציה אלא יוצאת ממנה וממשיכה לאיטרציה הבאה בקוד.
ניתן דוגמא:
נסו לחשוב, מה יקרה אם נריץ את הפונקציה..
האמת היא ש...
כלום.
למה ?
כי פונקצית Generator לא עושה כלום אם קוראים לה - אלא אם כן היא בתוך לולאה לפני הביטוי in.
למשל, כמו בקוד כאן:
אין תגובות:
הוסף רשומת תגובה