מחלקה ראשונה – עוד מתודות מיוחדות
__setitem__ ו- __getitem__
מחלקות הן דרך מעולה ליצור מודלים של פיסות מציאות קטנטנות, באמצעותן אנו יוצרים אובייקטים בעלי תכונות ויכולות כאילו היו..אובייקטים במציאות. כשאנו יוצרים אובייקט של רשימה בפייתון, נניח באופן הבא [x=[1,2, פייתון מפנקת אותנו בשלל אפשרויות שמובנות בתוכנה, למשל אנו יכולים לבצע (x.append(3 ולקבל רשימה מעודכנת [1,2,3]. כשאנו בונים אובייקט משל עצמנו, מחלקה פרטית, אנו צריכים לאפשר את כל הפעולות הללו באופן ידני. אם האובייקט שלנו לובש אופי של רשימה, ואנו רוצים שהוא יתנהג באופן דומה לדרך שבה אובייקטים מובנים בפייתון מתנהגים, אנו צריכים לשלב במחלקה מתודות מיוחדות (special methods) מתאימות.
__getitem__ מאפשרת לנו להגיע לאיברים ספציפיים בתוך האובייקט שלנו (שהוא כאמור מיכל בדומה לרשימה.
class SomeContainer: def __init__(self): self.contain=[1,2] def __getitem__(self, item): return self.contain[item] def __repr__(self): return f"{self.contain}" c=SomeContainer() for i in c: print(i)
>>>
1
2
למעלה, בנינו מחלקה בשם SomeContainer שבמהותה היא רשימה, המכילה מראש שני איברים בלבד המספרים 1 ו- 2. היא מכילה מתודה שכבר הכרנו בשם __repr__ כדי שנראה מה אנחנו מקבלים בתוך האובייקט. כתבנו מתודה מיוחדת בשם __getitem__ , וכעת אנו מייצרים אובייקט בשם c שיקבל את כל האפשרויות המטורפות שבנינו במחלקה, כמו למשל להדפיס את האיבר [c[0 שהוא המספר 1 או כמו שמודגם בתוכנית למעלה, לרוץ עם לולאת for ולהדפיס כל מה שיש ברשימה. בלי get item לא היינו מסוגלים להגיע לרגע המכונן הזה. אבל זה לא מספיק. כדי שהאובייקט יתנהג כמו list בפייתון, דרושות לנו תכונות נוספות.
__setitem__ יאפשר לנו להחליף איברים ברשימה (רק במיקום קיים, לא ניתן עדיין להוסיף איברים) – ננצל את האפשרות כדי להחליף את המספר 2 במספר שהוא התשובה לשאלת השאלות... – 42 (מי שלא הבין הוא לא מספיק זקן כדי להתקל בדוגלס אדמס)
class SomeContainer: def __init__(self): self.contain=[1,2] def __getitem__(self, item): return self.contain[item] def __repr__(self): return f"{self.contain}" def __setitem__(self,key, value): self.contain[key]=value c=SomeContainer() c[1]=42 print(c)
>>>
[1, 42]
טוב זה לא מספיק, בואו נייצר שתי פונקציות שיתמכו גם ב- insert (מאפשרת שתילה של איבר במיקום מסוים ברשימה) וגם ב- append – שמוסיפה איבר לסוף הרשימה בצד ימין. שימו לב שהן לא פונקציות מיוחדות -
class SomeContainer: def __init__(self): self.contain=[1,2] def __getitem__(self, item): return self.contain[item] def __repr__(self): return f"{self.contain}" def __setitem__(self,key, value): self.contain[key]=value def insert(self, location, value): self.contain.insert(location, value) def append(self, value): self.contain.append(value) c=SomeContainer() c.append(3) print(c) >>> [1, 2, 3] c.insert(2,42) print(c) >>> [1, 2, 42, 3]
למעלה, הוספנו את המספר 3 לרשימה, ושתלנו את המספר 42 במיקום השני ברשימה.
__delitem__ ו- __len__
לסיום האפיזודה הזאת נציין את __delitem__ המאפשרת באופן מפתיע...למחוק איברים ואת __len__ המאפשרת לנו למדוד את אורך הרשימה .
class SomeContainer: def __init__(self): self.contain=[1,2] def __getitem__(self, item): return self.contain[item] def __repr__(self): return f"{self.contain}" def __setitem__(self,key, value): self.contain[key]=value def insert(self, location, value): self.contain.insert(location, value) def append(self, value): self.contain.append(value) def __delitem__(self, key): del self.contain[key] def __len__(self): return len(self.contain) c=SomeContainer() c.append(3) print(c) >>> [1, 2, 3] del c[0] print(c) >>> [2, 3] print(len(c)) >>> 2
כל הטרחה הזאת בשביל שאובייקט יתנהג כמו רשימה פושטית בפייתון, וזה עוד לא הכל, בכל פעם שמשהו לא עובד צריך לבדוק איזו מתודה מיוחדת חסרה לנו, אולי __iter__ כדי להפוך את האובייקט לאיטרבלי, אולי __del__ שמוחקת את כל האובייקט ולא רק item בתוכו וכו'. פעמים רבות מתכנתים יזנחו את הטרחה ויישארו בתכנות הפונקציונלי שהוא כמו לטוס במחלקת תיירים לעומת טיסה במחלקה ראשונה. אני סבור שהמאמץ משתלם.