מה הקשר בין מחלקה שממומשת כ iterator, מתי generators שימושים לנו ומה בגדול ההבדלים - מדריך בסיסי
בפייתון יש דרך מאוד קלה להגדיר ״ג׳נרטור״, כשבגדול ג׳נרטור היא פונקציה שבמקום להחזיר ערך עושה לו yield שהתוצאה שלו הוא אובייקט מסוג ג׳נרטור שהוא איטרטבילי, כלומר, נוכל לשים אותו כערך שעליו רצים בלולאת for או נוכל להריץ עליו next לקבל את הערכים הבאים עד לסיום התנאים ויציאה מהפונקציה.
מחלקה שמוגדרת כאיטרטבל, היא מחלקה שנוכל לבצע עליה איטרציות (משל היתה נניח list) ואת זה משיגים כפי שנראה בהמשך באמצעות מימוש מתאים של המחלקה..
אז מתי נשתמש בג׳נרטור, מתי במחלקה שהיא איטרטבל ומתי לא באף אחד מהמקרים.
נתחיל ונגדיר שלרוב אפשר לא להשתמש באף אחד מהאופציות. בהנתן והגדרנו פונקציה שמחשבת עצרת (לרענון: העצרת של 3 היא 1*2*3 והעצרת של 5 היא 1*2*3*4*5) נוכל להחזיר מהפונקציה רשימה (שהיא כשלעצמה איטרטבלית) וזה כמובן יעבוד לנו.
אממה, ככל שהרשימה תהיה ארוכה יותר, המערכת תהיה פחות יעילה, כי בהרצת הקוד יווצר לנו liist מאוד גדול (ולא נהיה יעילים בעבודה מול הזכרון).
הנה הדוגמה של חישוב של עצרת שמחזירה list פייתוני סטנדרטי:
בדוגמה הזאת ה list שלנו הוא באורך של 10, אבל מה יקרה אם נעביר מספר גדול יותר, נניח 100, או 1000, או אפילו יותר? ברור שהיעילות שלנו בעבודה מול הזכרון פוחתת והיא למעשה מוגדרת כ-
O(n)
זאת היות והגודל המוקצה לזכרון יהיה ביחס ישר להגדלת ערך העצרת. אם העצרת תהיה 2 אז יהיו לנו שני איברים ואם 1000 אז 1000 איברים.
בואו נעשה את אותו הדבר, הפעם על class שממש פונקציות __next__ ו __iter___ מה שהופך את המחלקה הזו למחלקה איטרטבלית:
הפונקציה עובדת, ומאחורי הקלעים נמנענו מהחזקת ה list הארוך, כלומר אנחנו מבצעים כאן שיפור מהותי במפת הזכרון, הבעיה הפעם היא שהמימוש ארוך ומעט מסורבל לפעולה די פשוטה.
אז ג׳נרטור יתן לנו במקרה הזה להנות משני העולמות. גם לשפר את הביצועים וגם להגדיר מימוש מאוד פשוט וקריא:
ככל שנצטרך לשמור על סטייט בצורה מורכבת יותר וככל שנזדקק ללוגיקות מורכבות יותר, הפתרון של ג׳נרטור יתכן ויהפוך לפחות מתאים/נוח, אבל בדוגמה הפשוטה שלנו הוא פשוט מתאים בצורה מושלמת.
יש עוד על מה לדבר בנושא, למשל stream של מידע, אבל על כך אולי בפוסט אחר.
זו רק נקודה להתחיל ממנה להבין את הנושא.
אין תגובות:
הוסף רשומת תגובה