ירושה ו-super: Classes דומות בלי קוד כפול
- להסביר מתי ירושה חוסכת קוד כפול, ולבדוק אותה במבחן “הוא סוג של”
- להגדיר Parent ו-Child, ולהבין איך Python מחפשת מתודה — קודם ב-Child ואז ב-Parent
- לדרוס מתודה (Override) ב-Child כדי לשנות התנהגות
- להשתמש ב-
super()— גם ב-__init__וגם כדי להרחיב מתודה של ה-Parent
מה בשיעור
השיעור הזה בנוי כסיפור אחד, באותו סדר כמו במצגת:
- פרק ראשון — למה ירושה: אותו קוד פעמיים, ואיך יורשים במקום להעתיק
- פרק שני — דריסה ו-
super(): ירושה, אבל לא בדיוק — משנים רק חלק מההתנהגות
בסוף השיעור — תרגול, ומיני פרויקט שמחבר את שני הפרקים.
פרק ראשון: למה ירושה — Parent, Child ויחס הוא-סוג-של
מה נלמד?
- למה צריך ירושה: הבעיה של Classes כמעט זהות.
- Parent Class (מחלקת אב / Base Class) ו-Child Class (מחלקת בן / Subclass).
- התחביר של ירושה בפייתון.
- מה עובר בירושה.
- מבחן is-a: מתי ירושה מתאימה ומתי לא.
למה צריך את זה?
בנושא OOP – Classes ואובייקטים השבוע שאלנו: מה קורה כשמוסיפים “חשבון חיסכון” שדומה לחשבון רגיל, אבל עם ריבית? בגרסה הפרוצדורלית, כל פונקציה התמלאה ב-if account["type"] == .... בואו נראה את אותה בעיה ב-OOP, בלי ירושה:
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def monthly_cost(self):
return self.salary * 1.25
def describe(self):
return self.name + " earns " + str(self.salary)
class Manager:
def __init__(self, name, salary, team_size):
self.name = name
self.salary = salary
self.team_size = team_size
def monthly_cost(self):
return self.salary * 1.25
def describe(self):
return self.name + " earns " + str(self.salary)
def team_report(self):
return self.name + " manages " + str(self.team_size) + " people"Manager מעתיקה כמעט את כל Employee: name, salary, monthly_cost, describe. מה קורה כשעלות המעסיק משתנה מ-1.25 ל-1.3? צריך לתקן בשני מקומות, ואם יש גם Intern, Contractor ו-Director, בחמישה. זו הפרה של DRY ברמת ה-Classes.
ועוד דבר: מנהל הוא עובד. כל מה שנכון לעובד, נכון גם למנהל, ובנוסף יש לו עוד דברים. הקוד צריך לשקף את הקשר הזה.
הסבר מהבסיס
Inheritance (ירושה)
Inheritance = מנגנון שבו Class חדשה מקבלת אוטומטית את כל ה-Attributes והמתודות של Class קיימת, ויכולה להוסיף או לשנות חלקים.
- Parent Class (מחלקת אב, Base, Superclass) — ה-Class הכללית.
Employee. - Child Class (מחלקת בן, Derived, Subclass) — ה-Class הספציפית, שיורשת.
Manager.
יש לנו תבנית ייצור של “מכונית” (Parent): 4 גלגלים, מנוע, היגוי, בלימה. עכשיו רוצים “מכונית ספורט”. לא מתכננים מכונית מאפס. לוקחים את התבנית הקיימת, ומוסיפים ספוילר ומשנים את המנוע. כל מכונית ספורט היא מכונית (נוסעת, בולמת), אבל לא כל מכונית היא מכונית ספורט.
יחס “הוא סוג של” (is-a)
ירושה מתאימה רק כשאפשר לומר בכנות: “Child הוא סוג של Parent”.
| משפט | ירושה? |
|---|---|
| מנהל הוא סוג של עובד | ✓ Manager(Employee) |
| כלב הוא סוג של חיה | ✓ Dog(Animal) |
| חשבון חיסכון הוא סוג של חשבון בנק | ✓ SavingsAccount(BankAccount) |
| מכונית היא סוג של מנוע? | ✗ למכונית יש מנוע (has-a), וזו Composition, נושא תכנון OOP |
| הזמנה היא סוג של לקוח? | ✗ להזמנה יש לקוח |
מה עובר בירושה?
ה-Child מקבלת הכול מה-Parent:
- את
__init__(אם ה-Child לא מגדירה__init__משלה). - את כל המתודות.
- את ה-Class Attributes.
וה-Child יכולה:
- להוסיף מתודות ו-Attributes חדשים.
- לשנות (Override) מתודות קיימות. נלמד בפרק הבא, יחד עם
super().
חיפוש בשכבות — שוב
כשכותבים manager.describe(), Python מחפשת: (1) על האובייקט, (2) ב-Class שלו (Manager), (3) ב-Parent (Employee)… ועוצרת במקום הראשון שבו מצאה. זה אותו רעיון של חיפוש שכבות שראינו ב-LEGB ובסדר החיפוש של Attributes.
תחביר
class Parent:
def __init__(self, x):
self.x = x
def shared_method(self):
return ...
class Child(Parent): # Child inherits from Parent
def extra_method(self): # new method, only in Child
return ...
c = Child(5) # uses Parent.__init__ (Child has none of its own)
c.shared_method() # inherited
c.extra_method() # Child's own- שם ה-Parent בא בסוגריים אחרי שם ה-Child.
- Child בלי שום תוספת:
class Child(Parent): passהיא Class תקינה שזהה ל-Parent.
דוגמת קוד ראשונה
week-3/employees.py:
class Employee:
EMPLOYER_COST_RATE = 1.25
def __init__(self, name, salary):
self.name = name
self.salary = salary
def monthly_cost(self):
return self.salary * Employee.EMPLOYER_COST_RATE
def describe(self):
return self.name + " earns " + str(self.salary)
class Manager(Employee):
def team_report(self, team_size):
return self.name + " manages " + str(team_size) + " people"
dana = Employee("Dana", 12000)
avi = Manager("Avi", 20000)
print(dana.describe())
print(avi.describe())
print(avi.monthly_cost())
print(avi.team_report(5))פלט:
Dana earns 12000
Avi earns 20000
25000.0
Avi manages 5 peopleפירוק הקוד שורה אחר שורה
| קוד | הסבר |
|---|---|
class Manager(Employee): | Manager יורשת מ-Employee. |
Manager("Avi", 20000) | ל-Manager אין __init__ משלה, ולכן Python מוצאת את זה של Employee ומשתמשת בו. אבי מקבל name ו-salary. |
avi.describe() | אין describe ב-Manager. Python עולה ל-Employee ומוצאת אותה. self הוא עדיין אבי. |
avi.monthly_cost() | אותו דבר, וגם EMPLOYER_COST_RATE נמצא דרך ה-Parent. |
avi.team_report(5) | מתודה שקיימת רק ב-Manager. |
dana.team_report(...) | היה זורק AttributeError: עובד רגיל לא יורש מהמנהל. הירושה היא מלמעלה למטה בלבד. |
והתיקון של 1.25 → 1.3? משנים שורה אחת, ב-Employee, וכל המנהלים (ובעתיד, כל סוגי העובדים) מתעדכנים.
דוגמה נוספת
היררכיה עם כמה ילדים: Animal / Dog / Cat. week-3/animals.py:
class Animal:
def __init__(self, name, age):
self.name = name
self.age = age
def birthday(self):
self.age = self.age + 1
def describe(self):
return self.name + " (" + str(self.age) + " years old)"
class Dog(Animal):
def fetch(self):
return self.name + " brings back the ball"
class Cat(Animal):
def climb(self):
return self.name + " climbs the tree"
rex = Dog("Rex", 3)
mitzi = Cat("Mitzi", 5)
rex.birthday()
print(rex.describe(), "|", rex.fetch())
print(mitzi.describe(), "|", mitzi.climb())
for animal in [rex, mitzi]:
print(type(animal).__name__, "->", animal.describe())פלט:
Rex (4 years old) | Rex brings back the ball
Mitzi (5 years old) | Mitzi climbs the tree
Dog -> Rex (4 years old)
Cat -> Mitzi (5 years old)פירוק:
- שני ילדים לאותו Parent:
Dogו-Catחולקים את כל מה שמשותף (name,age,birthday,describe), וכל אחד מוסיף את מה שייחודי לו. rex.birthday()— מתודה שנכתבה פעם אחת ב-Animal, ומשנה את ה-State של rex.- בלולאה,
animal.describe()עובד על כל סוג שלAnimal, בלי לבדוק איזה סוג הוא. זה רמז ל-Polymorphism, שבו נעמיק בשיעור 3. type(animal).__name__— שם ה-Class כטקסט.
Animal
/ \
Dog Catסיכום
- Inheritance = Child Class מקבלת את כל ה-Attributes והמתודות של Parent Class, ויכולה להוסיף ולשנות.
- תחביר:
class Child(Parent):. - פותרת כפילות בין Classes דומות (DRY), ומשקפת את היחס “הוא סוג של” (is-a).
- Python מחפשת מתודה ב-Class של האובייקט, ואם לא מצאה, עולה ל-Parent.
- הירושה חד-כיוונית: Parent לא מכירה את מה שנוסף ב-Child.
- לא יורשים כשהיחס הוא “יש לו” (has-a). אז משתמשים ב-Composition (נושא תכנון OOP).
דוקומנטציה רשמית
- Python Tutorial — Inheritance — Parent ו-Child
- isinstance() — בדיקת יחס הוא-סוג-של
פרק שני: דריסה ו-super() — משנים רק את מה ששונה
מה נלמד?
super(): איך Child משתמשת בקוד של ה-Parent.__init__ב-Child: להוסיף Attributes בלי להעתיק את של ה-Parent.- Method Overriding (דריסה): Child מחליפה מתודה של ה-Parent.
- הרחבה: Child מוסיפה על המתודה של ה-Parent, במקום להחליף לגמרי.
למה צריך את זה?
בסוף השיעור הקודם נתקענו: ל-Truck צריך Attribute נוסף (cargo_kg), כלומר __init__ משלה. אבל אם נכתוב __init__ חדשה ב-Truck, היא מחליפה את זו של Vehicle, ואז brand ו-wheels לא יאותחלו, אלא אם נעתיק את הקוד. וזה בדיוק מה שירושה באה למנוע.
ובעיה שנייה: לפעמים ה-Child צריכה להתנהג אחרת באותה פעולה. מנהל מקבל בונוס בחישוב העלות. חשבון חיסכון לא מאפשר משיכה מתחת למינימום. צריך דרך לשנות מתודה שעברה בירושה.
הסבר מהבסיס
Method Overriding — דריסה
אם ב-Child מגדירים מתודה באותו שם כמו ב-Parent, הגרסה של ה-Child מנצחת עבור אובייקטים מסוג Child. זה נקרא Override (דריסה). למה? זוכרים את סדר החיפוש: Python מחפשת קודם ב-Class של האובייקט, ורק אם לא מצאה, עולה ל-Parent. אם המתודה קיימת ב-Child, Python בכלל לא מגיעה לגרסה של ה-Parent.
super() — “הגרסה של ה-Parent”
super() מחזירה אובייקט מיוחד שדרכו קוראים למתודה של ה-Parent, על אותו self. שתי השימושים העיקריים:
- ב-
__init__:super().__init__(...)— “תאתחל קודם את כל מה שה-Parent מאתחל”, ואז מוסיפים את מה שייחודי ל-Child. - בהרחבת מתודה:
super().describe()— “תביא לי את מה שה-Parent מחזיר”, ואז מוסיפים עליו.
סבתא (Parent) מכינה עוגת שוקולד לפי מתכון. אתם (Child) יורשים את המתכון. Override = אתם מחליטים להכין עוגה אחרת לגמרי באותו שם, “עוגת שוקולד”, אבל טבעונית, מאפס. הרחבה עם super() = “תעשו בדיוק כמו סבתא” (super().bake()), ואז להוסיף ציפוי שהמצאתם. לא מעתיקים את כל המתכון של סבתא. פשוט אומרים “כמו סבתא, ועוד…”.
שלוש אפשרויות למתודה ב-Child
| אפשרות | מה עושים | מתי |
|---|---|---|
| לרשת | לא כותבים כלום | ההתנהגות של ה-Parent מתאימה |
| לדרוס (Override) | כותבים מתודה באותו שם, בלי super() | ההתנהגות שונה לגמרי |
| להרחיב (Extend) | כותבים מתודה באותו שם, ובתוכה super().method() | “כמו ה-Parent, ועוד משהו” |
תחביר
class Parent:
def __init__(self, a):
self.a = a
def describe(self):
return "a=" + str(self.a)
def greet(self):
return "Hello from Parent"
class Child(Parent):
def __init__(self, a, b):
super().__init__(a) # let Parent set up self.a
self.b = b # then add what is new in Child
def describe(self): # EXTEND: reuse Parent's result
return super().describe() + ", b=" + str(self.b)
def greet(self): # OVERRIDE: replace completely
return "Hello from Child"שימו לב: ב-super().__init__(a) לא מעבירים self. super() כבר “יודע” על איזה אובייקט מדובר.
דוגמת קוד ראשונה
פותרים את בעיית ה-Truck. week-3/vehicles_super.py:
class Vehicle:
def __init__(self, brand, wheels):
self.brand = brand
self.wheels = wheels
def describe(self):
return self.brand + " with " + str(self.wheels) + " wheels"
class Truck(Vehicle):
def __init__(self, brand, max_load_kg):
super().__init__(brand, 6)
self.max_load_kg = max_load_kg
self.cargo_kg = 0
def load(self, kg):
if self.cargo_kg + kg > self.max_load_kg:
raise ValueError("over the limit: max " + str(self.max_load_kg) + " kg")
self.cargo_kg = self.cargo_kg + kg
def describe(self):
return super().describe() + ", carrying " + str(self.cargo_kg) + "/" + str(self.max_load_kg) + " kg"
volvo = Truck("Volvo", 1000)
volvo.load(600)
print(volvo.describe())
try:
volvo.load(500)
except ValueError as error:
print("Rejected:", error)
print(volvo.brand, volvo.wheels)פלט:
Volvo with 6 wheels, carrying 600/1000 kg
Rejected: over the limit: max 1000 kg
Volvo 6פירוק הקוד שורה אחר שורה
| קוד | הסבר |
|---|---|
def __init__(self, brand, max_load_kg): | ל-Truck יש __init__ משלה, עם פרמטרים שמתאימים למשאית. |
super().__init__(brand, 6) | “תריץ את __init__ של Vehicle על האובייקט הזה”. זה יוצר self.brand ו-self.wheels. משאית תמיד עם 6 גלגלים, ולכן ה-Child מחליטה את הערך. |
self.max_load_kg = ..., self.cargo_kg = 0 | רק מה שחדש ב-Truck. בלי להעתיק אף שורה מ-Vehicle. |
def describe(self): ב-Truck | הרחבה: אותו שם כמו ב-Vehicle. |
super().describe() | מקבל את "Volvo with 6 wheels" מה-Parent, ומוסיף לו את פרטי המטען. |
volvo.brand, volvo.wheels | קיימים, כי super().__init__ יצר אותם. |
אם Vehicle תשתנה מחר, למשל תוסיף self.year, משאיות יקבלו את זה אוטומטית דרך super().__init__. אילו העתקנו את הקוד, היינו צריכים לזכור לעדכן גם את Truck.
דוגמה נוספת
Override מלא, הרחבה, ו-super() בתוך כללים עסקיים. week-3/staff.py:
class Employee:
EMPLOYER_COST_RATE = 1.25
def __init__(self, name, salary):
self.name = name
self.salary = salary
def monthly_cost(self):
return self.salary * Employee.EMPLOYER_COST_RATE
def title(self):
return "Employee"
def describe(self):
return self.title() + " " + self.name + " - cost " + str(self.monthly_cost())
class Manager(Employee):
def __init__(self, name, salary, bonus):
super().__init__(name, salary)
self.bonus = bonus
def monthly_cost(self):
return super().monthly_cost() + self.bonus
def title(self):
return "Manager"
class Intern(Employee):
def monthly_cost(self):
return self.salary
def title(self):
return "Intern"
staff = [Employee("Dana", 12000), Manager("Avi", 20000, 3000), Intern("Noa", 5000)]
for person in staff:
print(person.describe())
print("Total monthly cost:", sum(person.monthly_cost() for person in staff))פלט:
Employee Dana - cost 15000.0
Manager Avi - cost 28000.0
Intern Noa - cost 5000
Total monthly cost: 48000.0פירוק:
Manager.monthly_cost— הרחבה: “כמו עובד רגיל (super().monthly_cost()= 25,000), ועוד בונוס”. אם שיעור עלות המעסיק ישתנה, המנהלים יקבלו את השינוי אוטומטית.Intern.monthly_cost— Override מלא: מתמחה לא נושא עלויות מעסיק, ולכן חישוב אחר לגמרי, בליsuper().title()— כל Class דורסת אותה.- הנקודה המעניינת:
describe()כתובה רק פעם אחת, ב-Employee. היא קוראת ל-self.title()ול-self.monthly_cost(). כשהאובייקט הוא Manager,self.monthly_cost()מגיעה לגרסה של Manager! המתודה של ה-Parent משתמשת אוטומטית בגרסאות הדרוסות של ה-Child. זה הבסיס של Polymorphism (השיעור הבא). Internלא הגדירה__init__, ולכן משתמשת בזו שלEmployee(ירושה רגילה).
סיכום
- Override = מתודה ב-Child באותו שם כמו ב-Parent. הגרסה של ה-Child מנצחת.
super()= גישה לגרסה של ה-Parent, על אותוself.- Child עם
__init__משלה חייבת לקרוא ל-super().__init__(...)(בליself), ואז להוסיף רק את מה שחדש. - הרחבה:
super().method()+ תוספת. דריסה מלאה: בליsuper(). self.method()בתוך מתודה דרוסה = רקורסיה. לגרסת ה-Parent:super().method().- מתודה של ה-Parent שקוראת ל-
self.x()תגיע אוטומטית לגרסה הדרוסה של ה-Child. - מתודה דרוסה שומרת על אותה חתימה (אותם פרמטרים).
דוקומנטציה רשמית
- super() — קריאה ל-Method של ה-Parent
- Python Tutorial — Inheritance — דריסת Methods
תרגול
התרגילים מבוססים רק על מה שנלמד בשיעור הזה ובשיעורים שלפניו. נסו לבד, ורק אז פתחו את הפתרון.
מה מתרגלים בדף הזה: ירושה — Parent ו-Child, מבחן “הוא סוג של”, הרחבה של __init__ עם super(), ודריסה של מתודה (Override).
בסוף הדף תדעו:
- לכתוב Child שיורש מ-Parent ומוסיף רק את מה שחדש
- להחליט מתי ירושה מתאימה, בעזרת המבחן “הוא סוג של”
- לקרוא ל-
super().__init__כדי שה-Parent יאתחל את החלק שלו - לדרוס מתודה, ולהשתמש בגרסה של ה-Parent בתוכה
תרגיל 1: עובד ומנהל
מה בונים: עובד ומנהל — כשמנהל הוא סוג של עובד, ולכן לא כותבים שוב את מה שמשותף.
מה לכתוב:
- Class בשם
Employeeעם שם ומשכורת. - ב-
Employee:describeשמחזירה שם ומשכורת, כמו בפלט. - Class בשם
Managerשיורשת מ-Employee— בלי__init__משלה. - ב-
Managerבלבד:manageשמקבלת גודל צוות ומחזירה משפט כמו בפלט.
קוד לבדיקה:
dana = Employee("Dana", 12000)
avi = Manager("Avi", 20000)
print(dana.describe())
print(avi.describe())
print(avi.manage(5))הפלט הרצוי:
Dana: 12000
Avi: 20000
Avi manages 5 peopleהצג פתרון
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def describe(self):
return self.name + ": " + str(self.salary)
class Manager(Employee):
def manage(self, team_size):
return self.name + " manages " + str(team_size) + " people"
dana = Employee("Dana", 12000)
avi = Manager("Avi", 20000)
print(dana.describe())
print(avi.describe())
print(avi.manage(5))Dana: 12000
Avi: 20000
Avi manages 5 peopleManager יורש את __init__ ואת describe — בלי לכתוב אותם שוב — ומוסיף רק את מה שחדש.
תרגיל 2: is-a — הוא סוג של
מה עושים: מחליטים — בלי קוד — מתי ירושה מתאימה.
המשימה: לכל זוג, ענו: האם הראשון צריך לרשת מהשני? הסבירו במשפט “X הוא סוג של Y” — ואם המשפט לא נכון, מה כן הקשר ביניהם.
Dogו-AnimalCarו-EngineSavingsAccountו-BankAccountSquareו-Color
מה מגישים: כן או לא לכל זוג, ומשפט הסבר אחד.
הצג פתרון
- כן — כלב הוא סוג של חיה.
- לא — מכונית לא סוג של מנוע. למכונית יש מנוע (has-a). זה מקרה להרכבה, לא לירושה.
- כן — חשבון חיסכון הוא סוג של חשבון בנק.
- לא — ריבוע לא סוג של צבע. אולי לריבוע יש צבע.
ירושה רק כשהמשפט “X הוא סוג של Y” נכון.
תרגיל 3: עץ חיות
מה בונים: עץ ירושה קטן — חיה, ושני סוגים של חיות שלכל אחד פעולה משלו.
מה לכתוב:
- Class בשם
Animalעם שם וגיל, ו-describeשמחזירה שם וגיל כמו בפלט. - Class בשם
Dogשיורשת מ-Animal, עםfetchשמחזירה את המשפט שבפלט. - Class בשם
Catשיורשת מ-Animal, עםclimbשמחזירה את המשפט שבפלט.
קוד לבדיקה:
rex = Dog("Rex", 4)
mitzi = Cat("Mitzi", 5)
print(rex.describe(), "|", rex.fetch())
print(mitzi.describe(), "|", mitzi.climb())הפלט הרצוי:
Rex (4 years old) | Rex brings back the ball
Mitzi (5 years old) | Mitzi climbs the treeהצג פתרון
class Animal:
def __init__(self, name, age):
self.name = name
self.age = age
def describe(self):
return self.name + " (" + str(self.age) + " years old)"
class Dog(Animal):
def fetch(self):
return self.name + " brings back the ball"
class Cat(Animal):
def climb(self):
return self.name + " climbs the tree"
rex = Dog("Rex", 4)
mitzi = Cat("Mitzi", 5)
print(rex.describe(), "|", rex.fetch())
print(mitzi.describe(), "|", mitzi.climb())Rex (4 years old) | Rex brings back the ball
Mitzi (5 years old) | Mitzi climbs the treeתרגיל 4: super().init
מה בונים: משאית — רכב שתמיד יש לו 6 גלגלים, ויש לו גם משקל מטען מקסימלי.
מה לכתוב:
- Class בשם
Vehicleעם מותג ומספר גלגלים. - Class בשם
Truckשיורשת מ-Vehicle. ה-__init__שלה מקבל מותג ומשקל מקסימלי. - בתוך
__init__שלTruck: קוראים ל-super().__init__עם המותג ו-6 גלגלים, ומאתחלים משקל נוכחי 0. load— מקבלת קילוגרמים ומוסיפה למשקל. אם עוברים את המקסימום:ValueErrorעם ההודעה שבפלט.
קוד לבדיקה:
truck = Truck("Volvo", 1000)
truck.load(600)
print(truck.brand, truck.wheels, truck.load_kg)
try:
truck.load(500)
except ValueError as error:
print("error:", error)הפלט הרצוי:
Volvo 6 600
error: overloaded: max 1000 kgהצג פתרון
class Vehicle:
def __init__(self, brand, wheels):
self.brand = brand
self.wheels = wheels
class Truck(Vehicle):
def __init__(self, brand, max_load_kg):
super().__init__(brand, 6)
self.max_load_kg = max_load_kg
self.load_kg = 0
def load(self, kg):
if self.load_kg + kg > self.max_load_kg:
raise ValueError("overloaded: max " + str(self.max_load_kg) + " kg")
self.load_kg = self.load_kg + kg
truck = Truck("Volvo", 1000)
truck.load(600)
print(truck.brand, truck.wheels, truck.load_kg)
try:
truck.load(500)
except ValueError as error:
print("error:", error)Volvo 6 600
error: overloaded: max 1000 kgsuper().__init__(brand, 6) מאתחל את החלק של ה-Parent. אחר כך ה-Child מוסיף את ה-Attributes שלו.
תרגיל 5: Override עם super
מה בונים: מנהל שהעלות החודשית שלו היא המשכורת ועוד בונוס.
מה לכתוב:
- Class בשם
Employeeעם שם ומשכורת, ו-monthly_costשמחזירה את המשכורת. - Class בשם
Managerשיורשת מ-Employee, ומקבלת גם בונוס. ב-__init__שלה קוראים ל-super().__init__. - ב-
Managerדורסים אתmonthly_cost: הגרסה של ה-Parent, דרךsuper(), ועוד הבונוס.
קוד לבדיקה:
print(Employee("Dana", 12000).monthly_cost())
print(Manager("Avi", 20000, 3000).monthly_cost())הפלט הרצוי:
12000
23000הצג פתרון
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def monthly_cost(self):
return self.salary
class Manager(Employee):
def __init__(self, name, salary, bonus):
super().__init__(name, salary)
self.bonus = bonus
def monthly_cost(self):
return super().monthly_cost() + self.bonus
print(Employee("Dana", 12000).monthly_cost())
print(Manager("Avi", 20000, 3000).monthly_cost())12000
23000super().monthly_cost() קורא לגרסה של ה-Parent. אם החישוב של המשכורת הבסיסית ישתנה, Manager יקבל את השינוי אוטומטית.
מיני פרויקט: עובדים בחברה
איזו מערכת בונים: מערכת עובדים לחברה. בחברה יש עובדים רגילים, מנהלים ומתמחים. כולם עובדים — יש להם שם ומשכורת — אבל כל סוג מוצג אחרת, ולמנהל יש גם בונוס. המערכת מציגה כל עובד ומחשבת את העלות החודשית של כולם יחד.
מה תלמדו מהפרויקט:
- לבנות Parent עם כל מה שמשותף, ו-Children שמוסיפים רק את מה ששונה
- לקרוא ל-
super().__init__מתוך ה-Child - לדרוס מתודה (Override), ולהרחיב מתודה עם
super() - לראות איך מתודה שנכתבה פעם אחת ב-Parent עובדת נכון לכל סוג
מה צריך לבנות — שלב אחרי שלב:
שלב 1 — עובד (Employee). __init__ מקבל שם ומשכורת. שלוש מתודות:
title— מחזירה את המילה Employee;monthly_cost— מחזירה את המשכורת;describe— מחזירה שם, תפקיד ועלות חודשית, כמו בפלט. היא משתמשת ב-titleוב-monthly_cost.
שלב 2 — מנהל (Manager). יורש מ-Employee ומקבל גם בונוס. ב-__init__ קוראים ל-super().__init__ ושומרים את הבונוס. דורסים את title — המילה Manager. מרחיבים את monthly_cost: התוצאה של super() ועוד הבונוס.
שלב 3 — מתמחה (Intern). יורש מ-Employee ומקבל גם מספר חודשי התמחות. ב-__init__ בודקים שהמספר בין 1 ל-12 — אחרת ValueError — ואז קוראים ל-super().__init__. דורסים את title — המילה Intern. את monthly_cost ואת describe לא כותבים: הן עוברות בירושה.
הודעת השגיאה — בדיוק כך:
months must be 1-12קוד לבדיקה:
staff = [
Employee("Dana", 12000),
Manager("Avi", 20000, 3000),
Intern("Noa", 6000, 6),
]
for person in staff:
print(person.describe())
try:
Intern("Tom", 5000, 0)
except ValueError as error:
print("error:", error)
total = 0
for person in staff:
total = total + person.monthly_cost()
print("Total monthly cost:", total)הפלט הרצוי:
Dana (Employee): 12000
Avi (Manager): 23000
Noa (Intern): 6000
error: months must be 1-12
Total monthly cost: 41000בסוף הפרויקט תדעו:
- לתכנן היררכיה: מה עולה ל-Parent ומה נשאר ב-Child
- להבחין בין שלוש אפשרויות למתודה ב-Child: לרשת, לדרוס, או להרחיב עם
super() - להבין למה
describeמציגה לכל עובד את התפקיד והעלות שלו, למרות שנכתבה פעם אחת
בונוס: הוסיפו ל-Manager מתודה manage שמקבלת גודל צוות ומחזירה משפט כמו Avi manages 5 people, והדפיסו אותה רק עבור המנהלים ברשימה.
הצג פתרון
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def title(self):
return "Employee"
def monthly_cost(self):
return self.salary
def describe(self):
return self.name + " (" + self.title() + "): " + str(self.monthly_cost())
class Manager(Employee):
def __init__(self, name, salary, bonus):
super().__init__(name, salary)
self.bonus = bonus
def title(self):
return "Manager"
def monthly_cost(self):
return super().monthly_cost() + self.bonus
class Intern(Employee):
def __init__(self, name, salary, months):
if months < 1 or months > 12:
raise ValueError("months must be 1-12")
super().__init__(name, salary)
self.months = months
def title(self):
return "Intern"
staff = [
Employee("Dana", 12000),
Manager("Avi", 20000, 3000),
Intern("Noa", 6000, 6),
]
for person in staff:
print(person.describe())
try:
Intern("Tom", 5000, 0)
except ValueError as error:
print("error:", error)
total = 0
for person in staff:
total = total + person.monthly_cost()
print("Total monthly cost:", total)Dana (Employee): 12000
Avi (Manager): 23000
Noa (Intern): 6000
error: months must be 1-12
Total monthly cost: 41000describeנכתבה פעם אחת ב-Employee, אבל כל עובד מקבל את ה-title()ואת ה-monthly_cost()שלו — כי Python מחפשת את המתודה קודם ב-Child.Manager.monthly_costמרחיבה:super().monthly_cost()מחזירה את המשכורת, ומוסיפים את הבונוס.titleדורסת לגמרי.Internלא כותב אתmonthly_cost— הוא יורש אותה כמו שהיא.
בונוס:
def manage(self, team_size):
return self.name + " manages " + str(team_size) + " people"(מתודה בתוך Manager.)
for person in staff:
if person.title() == "Manager":
print(person.manage(5))Avi manages 5 people