מדריך | פייתון קלאסים (Classes) - תכנות מונחה עצמים (OOP) המדריך המלא
-
המדריך הזה נכתב ללא בינה מלכותית (כן נעזרתי בו לחידוד הנקודות במדריך אבל בסוף זה כתיבה שלי)
הקדמה: כשאני עוד התעניינתי בפייתון במדריכי יוטיוב לא הצלחתי לקלוט את הנושא של קלאסים
כשהתחלתי סוף סוף ללמוד את זה מקצועי מאוד חששתי מהחלק הזה
אבל כשהגענו בקורס לחלק הזה זה נקלט כל כך בקלות אז הבנתי שכל מה שצריך זה הדרכה נכונה ולכן אני כותב כאן את זה
פייתון קלאסים (Classes) - תכנות מונחה עצמים (OOP) המדריך המלא
פרק 1 אז נתחיל. מה זה בכלל קלאסים?
אפשר לראות את הקלאסים כתבנית שאתה מגדיר לו מבנה כרצונך ואז יוצר הרבה הרבה יחידות (מופעים) במבנה הזה בתכנים משתנים
לדוגמה אתה יוצר קלאס מכונית שמוגדר עם תכונות אלו: חברה, צבע, קילומטרז
ואז אתה יוצר מופע אחד עם טיוטה, לבן, 150
ואז אתה יוצר עוד מופע עם יונדאי, שחור, 200
ואז עוד מופע עם מרצדס, אפור, 300
כך אנחנו לא צריכים להגדיר כל פעם מחדש את המבנה, אלא משתמשים בתבנית אחת ומייצרים ממנה אובייקטים שוניםדוגמת קוד עם הסבר שורה שורה
# יצירת קלאס class Car: # בניית האובייקט (constructor) (יוסבר לעומק בחלק 2) def __init__(self, company, color, km): self.company = company # שם החברה של הרכב self.color = color # צבע הרכב self.km = km # קילומטראז' # יצירת מופעים (אובייקטים) car1 = Car("Toyota", "White", 150) car2 = Car("Hyundai", "Black", 200) car3 = Car("Mercedes", "Gray", 300) print(type(car1)) # <class '__main__.Car'> print(car1.company) # Toyotaהסברים
כל מופע זה משתנה שמכיל אוביקט קלאס אם תעשו ```print(type(car1))זה יחזיר
<class '__main__.Car'>בחלק
print(car1.company)אתה ניגש לערך הספציפי למופע שלו קראת שבמיקום הקבוע בתבנית
כלומר שב-car1 הגדירוcar1 = Car("Toyota", "White", 150)אז המיקום שבקלאס
self.company = companyיהיה Toyota
מה שאומר שprint(car1.company)ידפיס Toyota
-
פרק 2 self
אז נתחיל עם self (תרגום לעברית של self זה עצמי (הכוונה לאובייקט המופע עצמו))
הוא בעצם אובייקט המופע שנוצר מהקלאס
כך שאם נעשהcar1 = Car("Toyota", "White", 150)car1 זה אובייקט שמכיל company, color , km
אז עם אתה רוצה לגשת אלcar1.companyאתה בעצם ניגש אל
self.companyכי self זה האובייקט שנוצר מהקלאס
עכשיו נניח שאתה עושה כך:car1 = Car("Toyota", "White", 150) car2 = Car("Hyundai", "Black", 200) car3 = Car("Mercedes", "Gray", 300)אז כל אובייקט זה self נפרד!
כך שה self שנקרא בשם car1 ב-self.companyיחזיר
"Toyota"אבל ה self שנקרא בשם car2 באותו מיקום
self.companyיחזיר
"Hyundai"וה- self שנקרא car3
באותו מקום בדיוקself.companyיחזיר
"Mercedes"אבל בשביל מה צריך את self?
למה לא להשתמש בשם האובייקט?
התשובה זה שכשאתה כותב קלאס אתה רוצה לקרוא בתוך הקלאס לחלק אחר שלו. ואז עוד אין מופעים!
וקלאס צריך לעבוד עם כל מופע. ולא רק עם שם מופע מסוים!
בשביל לחדד את זה נניח שיש קלאס כזהclass Fun_class: word_1 = "fun" word_2 = "class" def print_1(self): print(self.word_1) def print_2(self): print(self.word_2)אתה מעביר לפונקציה self
שזה אומר שכל מה שנמצא בקלאס כולל המשתנה והפונקציה עצמה עוברים לפונקציה
כך שבתוך הפונקציה אתה יכול לקרוא לאוביاקט self על כל תוכנו
מה שמאפשר לך לקרוא למשתנים הראשיים ולפונקציה השנייה מתוך הראשונה
בגלל שהמשתנים ושני הפונקציות הם חלק מה-self והפונקציות קיבלו את אותו ה-self
ועוד יותר מזה אתה יכול לשנות את המשתנה הראשי מתוך הפונקציה!
כי זה מתפקד כמשתנה גלובלי לכל המופע
דוגמהclass Fun_class: word = "fun" def print(self): print(self.word) def change_word(self): self.word = "fun_class" Fun_obg = Fun_class() Fun_obg.print() Fun_obg.change_word() Fun_obg.print()בדוגמה זו רואים שהפונקציה change_word משנה את המשתנה word הגלובלי לכל ה-self אבל לא ל-self (כלומר מופע) אחר
אבל איך self עובד מאחורי הקלעים?
נניח שעכשיו עושים כך
Fun_obg = Fun_class() Fun_obg.print()אז פייתון יעשה מאחורי הקלעים כך
Fun_class.print(Fun_obg)שהוא בעצם מריץ את הקלאס הקבוע על self-מופע ספציפי
-
פרק 3 Constructor - בנאי
אז הבנאי (Constructor) זו פונקציה שמוגדרת בקלאס ורצה על כל מופע בזמן יצירתו
מה שמאפשר להגדיר משתנים, לקבל פרמטרים, ולעשות לוגיקה על כל מופע אוטומטי ביצירה
ההגדרה נעשה כךclass Fun_class: def __init__(self): self.word = "fun"בתוך פונקציית init אתה מכניס את כל הדברים שאתה רוצה לעשות אוטומטי על כל המופעים
קבלת פרמטרים
ביצירת בנאי אתה יכול להעביר לבנאי פרמטרים כך
class Fun_class: def __init__(self, word): self.word = wordמה שקורה בקוד זה בשורה הראשונה מגדירים קלאס
class Fun_class:בשורה השנייה מגדירים בנאי (בגלל שזה הבנאי אז כל פרמטר בפונקציה זה פרמטר לקלאס)
def __init__(self, word):ובשורה השלישית מוסיפים את תוכן הפרמטרים ל-self (ובגלל שזה הבנאי זה יוגדר אוטומטית לכל מופע ביצירתו)
self.word = wordואז ביצירת קלאס מעבירים את הפרמטר כך
Fun_obg = Fun_class("fun") # "fun" זה הפרמטר -
פרק 4 משתני self מול משתני class
על משתני self כבר נכתב בפרק 2 שזה משתנים שמוגדרים בקלאס ותוכנם משתנה ממופע למופע
אז מה זה משתני קלאס?
משתני קלאס זה משתנים שגם מוגדרים בקלאס אבל תוכנם קבועים לכל המופעים
כלומר שאם תשנה את זה במופע אחד זה ישתנה בכל המופעים
בשביל לכתוב כזה משתנה פשוט תורידו את ה-self
לדוגמהclass Fun_class: word = "fun"ועכשיו אם תעשה
Fun_obg_1 = Fun_class() Fun_obg_2 = Fun_class() Fun_class.word = "class" print(Fun_obg_1.word) # יחזיר class print(Fun_obg_2.word) # גם יחזיר classשני המופעים ישתנו כי שינית את הקלאס עצמו
אבל אם תעשה את השינוי דרך המופע כךFun_obg_2.word = classesזה לא ישנה את הקלאס אלא ייצור משתנה חדש בתוך ה-self של המופע הזה
-
פרק 5 מתודות: מתוודות רגילות, מתודות סטטיות, ומתודות קלאס
אז כל פונקציה בקלאס נקראת מתודה
ומגדירים אותו כךclass Fun_class: def print_fun(self): print("This is a function in a class")ובשביל להפעיל צריך ליצור מופע כך
obj = Fun_class()ואז אפשר להפעיל את המתודה כך
obj.print_fun()אז למה צריך להעביר למתודה רגילה self?
כי self זה אובייקט המופע ולכן כל פעם שמפעילים את הקלאס.פונקציה אז בעצם מפעילים את הקלאס.פונקציה על המופע-self שממנו קוראים לזה (ואני ישים בספוילר את ההסבר של איך עובד self ממה שכבר נכתב בסוף פרק 2)
אבל איך self עובד מאחורי הקלעים?
נניח שעכשיו עושים כךFun_obg = Fun_class() Fun_obg.print()אז פייתון יעשה מאחורי הקלעים כך
Fun_class.print(Fun_obg)שהוא בעצם מריץ את הקלאס הקבוע על self-מופע ספציפי
מתודות סטטיות
מטודות סטטיות אלו פונקציות קלאס סטטיות
כשמגדירים אותם אין פרמטר חובה (בשונה מהמתודות הרגילות שחיבות פרמטר self)
שזה אומר שהם לא קשורים אל ה-self-מופע
המתודות-פונקציות האלו נקראים ללא מופע
אלא ישירות מהשם של הקלאס
אבל בגלל שזה לא קשור ל-self גם אין לזה קשר לשאר הקלאס
כלומר שזה מתנהג כמו פונקציה רגילה שנמצאת בקלאס רק לאירגון
הם נכתבים פשוט עם @staticmethod בשורה לפני פתיחת הפונקציה
דוגמהclass Fun_class: @staticmethod def print_word(): print("word")ועכשיו תוכל לקרוא לזה מהשם של הקלאס ישירות ללא מופע
דוגמה לקריאהFun_class.print_wordאבל בשביל מה זה שימושי מתודות סטטיות?
אז השימושיות הכי נפוצה למתודה סטטית זה כשהפונקציה-מתודה עושה לוגיקה שקשורה לקלאס בלי לגשת לנתוני המופע
מתודות קלאס
מתודות קלאס אלו מתודות שבמקום אובייקט מסוג self הם מקבלים אובייקט מסוג cls
אז מה זה אובייקט cls?
cls זה כפשוטו אובייקט הקלאס עצמו (במקום אובייקט המופע שזה self)
שזה אומר שהם גם לא קשורים אל ה-self-מופע
אבל הם גם לא מנותקים משאר הקלאס
מה שאומר שיש להם גישה למשתני אובייקט (ראה פרק 4)
אבל אין להם גישה למשתני self-מופע
כשמגדירים אותם חייבים להוסיף פרמטר cls (במקום self במתודות רגילות)
הם נקראים ישירות מהשם של הקלאס (כמו מתודה סטטית)
אבל יש להם גישה לאובייקט cls-קלאס של הראשי (ולא של המופע)
בשונה ממתודה סטטית שאין לזה בכלל גישה לשאר הקלאס
הם נכתבים פשוט עם @classmethod בשורה לפני פתיחת הפונקציה ופרמטר cls
דוגמהclass Fun_class: word = "hello" # בלי self! @classmethod def print_word(cls): # פרמטר cls print(cls.word) # כמו self.word רק מהקלאס ישירות ולא ממופעועכשיו תוכל לקרוא לזה מהשם של הקלאס ישירות ללא מופע
דוגמה לקריאהFun_class.print_wordסיכום ההבדלים בין מתודות רגילות, מתודות סטטיות, ומתודות קלאס
הרגיל עובד על מופע ספציפי ומשתנה ממופע למופע
הסטטי הוא קבוע ולא מכיר את שאר הקלאס
והקלאס הוא סוג ביניים שעובד על אובייקט הקלאס עצמו כך שהוא לא לפי מופע אבל מכיר את שאר הקלאסאבל מתי להשתמש בכל סוג?
אז תקחו כלל אצבע
אם המתודה (פונקציה בקלאס) צריכה הגדרות שמשתנות ממופע למופע תקחו רגילות
אם המתודה (פונקציה בקלאס) צריכה הגדרות משותפות לכל המופעים תקחו קלאס
אם המתודה (פונקציה בקלאס) לא צריכה לא צריכה דברים מחלקים אחרים מהקלאס תקחו סטטיות -
פרק 6 Encapsulation - הסתרת מידע
אז איך מסתרים על תוכן מהקלאס?
אז יש שני דרכים1 _var (קו תחתון לפני הגדרת המשתנה)
זה נכתב כך
class printer: _word = "hello"שימו לב שזה לא משנה כלום בהתנהגות הקוד!
אלא רק משמש כסימן מוסכם שזה משתנה פנימי שלא אמורים לשנות מבחוץ2 __var (שני קווים תחתונים לפני הגדרת המשתנה)
זה כבר הפחתת טעויות אמיתית אבל בתור הסתרה זה נעקף בקלות
זה נכתב כךclass printer: __word = "hello"וזה בעצם משנה את השם שנשמר באחורי הקלעים משם המשתנה
__wordאל שם קו תחתון שם הקלאס ואז השם שכולל שני קווים תחתונים לפני
כלומר שזהclass printer: __word = "hello"נשמר למעשה (באחורי הקלעים) כך
_printer__wordאבל למה זה טוב בכלל?
התשובה זה למנוע טעיות של התנגשות ודריסות שמות
אבל זה לא מנגנון אבטחה!
כי ברגע שאתה יודע את שם הקלאס ושם המשתנה אתה יכול פשוט להרכיב אותם במבנה הקבוע וכך להגיע לזה -
פרק 7 property מנגנון שליטה בגישה למשתנה
אז מה זה property?
property בעצם מאפשר לך לגשת לפונקציה כאילו שזה משתנה
בעצם אתה עושה @property שורה לפני ואז אתה עושה פונקציה ששם הפונקציה זה שם המשתנה ותוכן המשתנה זה מה שהפונקציה הזו תחזיר ב-return
כך שכשנגדיר כךclass printer: def __init__(self, word): self._word= word @property def word(self): return "word"הטייפ של ההבטחה word זה תוכן ה-return כי זה לא פונקציה אלא משתנה שמתחבא בפונקציה
ולכן כל פעם שנעשה אחרי זה כךobj = printer(10) print(obj.word)זה יקרא את word כפונקציה ויחזיר את ה-return כמשתנה
אז בשביל מה צריך את זה?
התשובה זה שאתה יכול להכניס לוגיקה שלפיו זה יחזיר משהו
זה אומר שאני יכול להגדיר כךclass printer: def __init__(self, num): self.num = num @property def word(self): if self.num > 5: return "word" else: return "none"עכשיו זה בעצם יבדוק בכל מופע שבו מפעילים את זה ואם num גדול מ-5 יכיל המשתנה word אחרת יכיל המשתנה none
שימו לב שדבר ראשון יש להגדיר את המשתנה עם קו תחתון לפני
ואחרי זה מגדירים property ששולט על שלושה דברים במשתנה. הצגה, עריכה, ומחיקה
ההגדרת property מגדיר את המשתנה ומשמש כ-getter
אם תחסר עריכה לא תוכל לערוך (שזה טוב לפעמים)
אם תחסר מחיקה לא תוכל למחוק (שזה גם טוב לפעמים)
אז איך עושים עריכה ומחיקה?
אז עריכה עושים כך@word.setter def word(self, val): self._word = valואז אתה יכול להוסיף לוגיקה לפני כל שינוי
דוגמה@word.setter def word(self, val): if val == "abc": self._word = valואותו רעיון למחיקה
@word.deleter def word(self): del self._wordוגם כאן ניתן להכניס לוגיקה
דוגמה@word.deleter def word(self): if self._word: del self._word -
פרק 8 ירושה וקומפוזיציה
אז ירושה וקומפוזיציה הם שני שיטות להעביר קלאס אחד אל תוך השני
אז מה זה ירושה?
בירושה אתה בעצם מגדיר קשר בין מחלקות בזמן ההגדרה
כך שהשני בעצם מקבל גישה לכל מה שיש לראשון ומרחיב אותו
ירושה זה בעצם שהבן הוא סוג של האבא
מגדירים את הירושה כךclass Father: def father_print(self): print("father") class Son(Father): # מוסיפים בסוגריים את שם קלאס האב def son_print(self): print("son")בעצם מוסיפים בהגדרת קלאס הבן סוגריים שבתוכו שמים את קלאס האב
ועכשיו תוכל להשתמש בקלאס בן בכל הדברים שיש בקלאס האב כאילו שזה שלו
דוגמהson_obj = Son() son_obj.father_print()אז מה זה קומפוזיציה?
אז בקומפוזיציה אתה בעצם יוצר קלאס אחד שיש לו משתנה של אובייקט מופע הקלאס השני##
כך שהוא יכול לגשת אל זה ב-self ולהשתמש בו כמו במופע של אובייקט אחר שנמצא בתוך המחלקה
דוגמהclass Engine: def start(self): print("start") class Car: def __init__(self): self.engine = Engine() def start(self): self.engine.start() # שימוש במופע של מחלקה אחרתכך בעצם משתמשים בקלאס מנוע בתוך ה-self של הקלאס רכב
אז מתי להשתמש בכל סוג?
אז תיקחו כלל אצבע
מתי שהקלאס המקבל הוא סוג של הקלאס הנותן (כמו שכלב הוא סוג של חיה ורולקס זה סוג של שעון) משתמשים בירושה
אבל מתי שהמקבל הוא לא "סוג של" באמת תעדיפו קומפוזיציהואוד כלל אצבע חשוב. תמיד תעדיפו קומפוזיציה!
אז למה הכלל הזה?
התשובה זה שירושה יוצרת קשר מאוד חזק שעלול לשבור את הקוד וליצור "עץ משפחתי" מאוד מורכב
קומפוזיציה לאומת זאת מאפשרת חיבור יותר גמיש -
פרק 9 Method Overriding - עקיפת שיטה ו-super() ו MRO - סדר החיפוש בירושה
אז מה זה Method Overriding?
Method Overriding זה כשקלאס אחד יורש שני ואז משתמש בגרסה משלו בפונקציה מהשני תוך דריסת הגרסה המקורית
ואני יכתוב דוגמה לירושה רגילה קודם שיראו את ההבדלclass Animal: def speak(self): print("צליל כללי") class Dog(Animal): pass dog = Dog() dog.speak()ואז הפלט יהיה
צליל כלליזה דוגמה לירושה רגילה שבו הבן משתמש בפונקציה מהאבא
ועכשו נראה איך הבן דורס את הפונקצית אבא לטובת גרסה משלו
דוגמהclass Animal: def speak(self): print("צליל כללי") class Dog(Animal): def speak(self): print("הב הב!") dog = Dog() dog.speak()ואז הפלט יהיה
הב הב!מה שקורה כאן זה שמגדירים בבן פונקציה (מטודה) באותו שם של מתודת האב
מה שגורם לדריסת הגרסה מהאב ויצירת גרסת בן חדשהאבל למה זה שימושי בכלל?
וכן זה שימושי בשביל ליצור מבנה כללי בקלאס האב שתוכנו ישתנה מבן לבן
שימוש ב-super()
אז מה זה super()?
אז super() זה כשאתה לא רוצה לדרוס לגמרי את פונקצית האב אלא לקחת אותו ולהסיף לו כרצונך
דוגמהclass Animal: def speak(self): print("צליל כללי") class Dog(Animal): def speak(self): super().speak() print("הב הב!")הפלט יהיה
צליל כללי הב הב!בעצם מה ש-super() עושה זה לטעון את גרסת האב לתוך גרסת הבן שדורסת את גרסת האב
סדר החיפוש (MRO)
אז מה זה בכלל MRO?
אז MRO זה הסדר שבו פייתון מחפש מתודות ומאפיינים בקלאסים
לדוגמה בקוד הזהclass A: def hello(self): print("A") class B(A): def hello(self): print("B") super().hello() class C(B): def hello(self): print("C") super().hello()אז כשאתה קורא ל-
super().hello()מהיכן הוא יודע לאיזה אוביקט לפנות, לבן או לאבא או לסבא?
וזה בדיוק MRO
שזה סדר חיפוש קבוע שכשאתה קורא למתודה זה בודק. יש אותו באובייקט עצמו? לא? הולך לאבא. יש אותו לאבא? לא? הולך לסבא. יש אותו בסבא? לא? מחזיר שגיאה.ואם אתה רוצה לדעת מה סדר החיפוש בירושה ספציפית?
אז פשוט תבדוק כך
print(C.__mro__)ועל הדוגמה לעיל יהיה הפלט
(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)כלומר שסדר החיפוש זה
C → B → A → object -
פרק 10 Polymorphism - ריבוי צורות
אז מה זה Polymorphism?
Polymorphism זה כשעושים כמה קלאסים אם אותו שם פונקציה-מטודה כשבכל קלאס זה עושה משהו אחר
למשל בדוגמה זוclass Dog: def speak(self): print("הב הב") class Cat: def speak(self): print("מיאו")איך אתה יכול לעבור על אותו הפונקצית speak בכל הקלאסים בצורה אחידה?
אז עם Polymorphism אתה פשוט עושה ליסט של מופעים (לא צריך ליצור מופעים ואז להכניס לליסט אלא אפשר לעשות רשימה שמכילה יצירת מופעים)animals = [Dog(), Cat()] # יצירת רשימת מופעים for animal in animals: animal.speak()בעצם אתה עובר על כל המופעי קלאס ברשימה וניגש לאותו השם בכולם
שימוש ב-Polymorphism בירושה
נניח שיש קלאס אב כזה
class fader: def name(self): passואז שלושה יורשים כך
class son_1(fader): def name(self): return "son_1" class son_2(fader): def name(self): return "son_2" class son_3(fader): def name(self): return "son_3"בקוד זה כל בן דורס את הפונקציה name שבאב לגרסה שלו
קוד זה בעצם משתמש באב למבנה קבוע שתוכנו משתנה אצל כל בן
אבל אני רוצה לעבור על הגרסאות של כל הבנים. איך אני עושה את זה?
אז בלי Polymorphism זה יראה כךson_1_instance = son_1().name() son_2_instance = son_2().name() son_3_instance = son_3().name()ואם Polymorphism זה יראה כך
list_of_sons = [son_1(), son_2(), son_3()] for son in list_of_sons: son.name() -
פרק 11 Dunder Methods (Magic Methods)
אז מה זה Dunder Methods (Magic Methods)?
Dunder Methods (Magic Methods) אלה מטודות מובנות שמאפשרות להגדיר את הקלאס
כותביים אותם כשם פונקציה עם שני מקף תחתון () לפני השם הקבוע ושני מקף תחתון () אחרי השם הקבוע
נניח שיש לנו קלאס כזהclass cla: passומופע כזה
class_obj = cla()ואני יכתוב כאן את הנפוצות
1. init – בנאי (Constructor)
אותו אנחנו כבר מכירים
הוא מגדיר מה ירוץ על כל המופעים מיד עם יצירתם
אם לא תגדירו לא ירוץ אטומטית כלום2. str – איך האובייקט מוצג
זה מגדיר מה יודפס במקרה שתעשה print על האוביקט קלאס
print(class_obj)כך שמה שיחזור ל return זה מה שיודפס
אם לא תגדירו אותו הפלט לprint יהיה משהו כמו<__main__.A object at 0x000001F3A9C>3. repr – ייצוג למפתחים
זה כמו str אבל יותר טכני למפתחים
4. len – פונקציית len()
זה מגדיר מה יקרה אם נעשה len על האוביקט קלאס
len(class_obg)אם לא נגדיר זה יחזיר שגיאה
5. add – חיבור (+)
זה מגדיר מה יקרה אם נחבר אוביקטים כך
a = Number(5) b = Number(3) c = a + b print(c.value)6. eq – השוואה (==)
זה מגדיר מה יקרה אם נשווה בין אוביקטים כך
p1 = Person("A") p2 = Person("A") print(p1 == p2)7. getitem – גישה עם []
מגדיר מה יקרה אם ניגש לאוביקט כך
class_obj[1]8. setitem – שינוי עם []
מגדיר מה יקרה אם ננסה לשנות את האוביקט כך
class_obj[1] = 39. call – להפוך אובייקט לפונקציה
דוגמה
class Greeter: def __call__(self, name): print(f"שלום {name}") g = Greeter() g("ישראל") -
נא לא להגיב כאן כל משוב יכול להכתב בנושא תגובות בקישור
https://mitmachim.top/topic/96753/מדריך-פייתון-קלאסים-classes-תכנות-מונחה-עצמים-oop-תגובות -
י יוסי מחשבים נעל נושא זה