Наследование и super: похожие классы без дублирования кода
- Объяснить, когда наследование экономит дублирующийся код, и проверять это тестом «является разновидностью»
- Определять родительский (Parent) и дочерний (Child) класс и понимать, как Python ищет метод — сначала в дочернем, затем в родительском
- Переопределять метод в дочернем классе, чтобы изменить поведение
- Использовать
super()— и в__init__, и чтобы расширить метод родителя
В этом уроке
Урок построен как одна история, в том же порядке, что и в презентации:
- Глава 1 — зачем наследование: один и тот же код дважды и как наследовать вместо копирования
- Глава 2 — переопределение и
super(): наследование, но не совсем — меняем только часть поведения
В конце урока — практика и мини-проект, который связывает обе главы.
Глава 1: Зачем наследование — родитель, потомок и отношение «является разновидностью»
Что мы изучим?
- Зачем нужно наследование: проблема почти одинаковых классов.
- Родительский класс (Parent Class, Base Class) и дочерний класс (Child Class, Subclass).
- Синтаксис наследования в Python.
- Что передаётся при наследовании.
- Тест is-a: когда наследование подходит, а когда нет.
Зачем это нужно?
В уроке о классах и объектах мы спрашивали: что будет, если добавить «сберегательный счёт», похожий на обычный, но с процентами? В процедурной версии каждая функция наполнялась if account["type"] == .... Посмотрим на ту же проблему в ООП, без наследования:
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 на уровне классов.
И ещё одно: менеджер — это сотрудник. Всё, что верно для сотрудника, верно и для менеджера, а у менеджера к тому же есть дополнительное. Код должен отражать это отношение.
Объяснение с основ
Наследование
Наследование (Inheritance) = механизм, при котором новый класс автоматически получает все атрибуты и методы существующего класса и может добавлять или менять части.
- Родительский класс (Parent, Base, Superclass) — общий класс.
Employee. - Дочерний класс (Child, Derived, Subclass) — частный класс, который наследует.
Manager.
У нас есть шаблон производства «машины» (родитель): 4 колеса, двигатель, руль, тормоза. Теперь нам нужна «спортивная машина». Мы не проектируем машину с нуля. Мы берём существующий шаблон и добавляем спойлер, а двигатель меняем. Каждая спортивная машина является машиной (она едет, тормозит), но не каждая машина спортивная.
Отношение «является разновидностью» (is-a)
Наследование подходит, только когда можно честно сказать: «потомок — разновидность родителя».
| Предложение | Наследование? |
|---|---|
| Менеджер — разновидность сотрудника | ✓ Manager(Employee) |
| Собака — разновидность животного | ✓ Dog(Animal) |
| Сберегательный счёт — разновидность банковского счёта | ✓ SavingsAccount(BankAccount) |
| Машина — разновидность двигателя? | ✗ У машины есть двигатель (has-a), а это композиция (Composition), тема проектирования ООП |
| Заказ — разновидность клиента? | ✗ У заказа есть клиент |
Что передаётся при наследовании?
Потомок получает от родителя всё:
__init__(если потомок не определяет собственный__init__).- Все методы.
- Атрибуты класса.
А потомок может:
- Добавлять новые методы и атрибуты.
- Менять (переопределять, Override) существующие методы. Изучим это в следующей главе вместе с
super().
Поиск по слоям — снова
Когда вы пишете manager.describe(), Python ищет: (1) на объекте, (2) в его классе (Manager), (3) в родителе (Employee)… и останавливается на первом месте, где нашёл. Это та же идея поиска по слоям, что мы видели в LEGB и в порядке поиска атрибутов.
Синтаксис
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- Имя родителя пишется в скобках после имени потомка.
- Потомок без всяких дополнений:
class Child(Parent): pass— допустимый класс, идентичный родителю.
Первый пример кода
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 находит __init__ у Employee и использует его. Ави получает name и salary. |
avi.describe() | В Manager нет describe. Python поднимается к Employee и находит его. self по-прежнему Ави. |
avi.monthly_cost() | То же самое, и EMPLOYER_COST_RATE тоже находится через родителя. |
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)Разбор:
- Два потомка одного родителя:
DogиCatразделяют всё общее (name,age,birthday,describe), и каждый добавляет своё уникальное. rex.birthday()— метод, написанный один раз вAnimal, который меняет состояние rex.- В цикле
animal.describe()работает для любого видаAnimal, без проверки, что это за вид. Это намёк на полиморфизм, в который мы углубимся в следующем уроке. type(animal).__name__— имя класса в виде текста.
Animal
/ \
Dog CatИтоги
- Наследование = дочерний класс получает все атрибуты и методы родительского класса и может добавлять и менять.
- Синтаксис:
class Child(Parent):. - Оно решает проблему дублирования между похожими классами (DRY) и отражает отношение «является разновидностью» (is-a).
- Python ищет метод в классе объекта, а если не нашёл — поднимается к родителю.
- Наследование однонаправленное: родитель не знает, что добавлено в потомке.
- Не наследуйте, когда отношение — «имеет» (has-a). Тогда используют композицию (тема проектирования ООП).
Официальная документация
Глава 2: Переопределение и super() — меняем только то, что отличается
Что мы изучим?
super(): как потомок использует код родителя.__init__в потомке: добавление атрибутов без копирования родительских.- Переопределение метода (Method Overriding): потомок заменяет метод родителя.
- Расширение (Extending): потомок дополняет метод родителя, а не заменяет его полностью.
Зачем это нужно?
Здесь простое наследование упирается в стену: Truck, который наследует Vehicle, нуждается в дополнительном атрибуте (cargo_kg), то есть в собственном __init__. Но если написать новый __init__ в Truck, он заменит __init__ Vehicle, и тогда brand и wheels не будут инициализированы — если только мы не скопируем код. А именно это наследование и должно было предотвратить.
И вторая проблема: иногда потомок должен вести себя по-другому в том же действии. Менеджер получает бонус в расчёте стоимости. Сберегательный счёт не позволяет снять ниже минимума. Нужен способ изменить метод, пришедший по наследству.
Объяснение с основ
Переопределение метода
Если в потомке определить метод с тем же именем, что и в родителе, для объектов типа Child побеждает версия потомка. Это называется переопределением (Override). Почему? Вспомните порядок поиска: Python ищет сначала в классе объекта и только если не нашёл, поднимается к родителю. Если метод есть в потомке, Python даже не доходит до версии родителя.
super() — «версия родителя»
super() возвращает специальный объект, через который вызывают метод родителя на том же self. Два главных применения:
- В
__init__:super().__init__(...)— «сначала инициализируй всё, что инициализирует родитель», а затем добавь то, что уникально для потомка. - При расширении метода:
super().describe()— «принеси мне то, что возвращает родитель», а затем дополни.
Бабушка (родитель) печёт шоколадный торт по рецепту. Вы (потомок) наследуете рецепт. Переопределение = вы решаете испечь совсем другой торт с тем же названием «шоколадный торт», но веганский, с нуля. Расширение с super() = «сделай точно как бабушка» (super().bake()), а затем добавь глазурь, которую придумали вы. Вы не копируете весь бабушкин рецепт. Вы просто говорите «как у бабушки, плюс…».
Три варианта для метода в потомке
| Вариант | Что вы делаете | Когда |
|---|---|---|
| Унаследовать | Ничего не пишете | Поведение родителя подходит |
| Переопределить (Override) | Пишете метод с тем же именем, без super() | Поведение совсем другое |
| Расширить (Extend) | Пишете метод с тем же именем, а внутри — super().method() | «Как у родителя, плюс что-то» |
Синтаксис
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 колёс, поэтому значение решает потомок. |
self.max_load_kg = ..., self.cargo_kg = 0 | Только то, что ново в Truck. Без копирования ни одной строки из Vehicle. |
def describe(self): в Truck | Расширение: то же имя, что и в Vehicle. |
super().describe() | Получает "Volvo with 6 wheels" от родителя и дополняет данными о грузе. |
volvo.brand, volvo.wheels | Существуют, потому что их создал super().__init__. |
Если завтра Vehicle изменится, например добавит self.year, грузовики получат это автоматически через super().__init__. Если бы мы скопировали код, пришлось бы помнить, что нужно обновить и Truck.
Ещё один пример
Полное переопределение, расширение и 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— полное переопределение: стажёр не несёт расходов работодателя, поэтому совершенно другой расчёт, безsuper().title()— каждый класс переопределяет его.- Интересный момент:
describe()написан только один раз, вEmployee. Он вызываетself.title()иself.monthly_cost(). Когда объект — Manager,self.monthly_cost()доходит до версии Manager! Метод родителя автоматически использует переопределённые версии потомка. Это основа полиморфизма (следующий урок). Internне определил__init__, поэтому использует__init__классаEmployee(обычное наследование).
Итоги
- Переопределение (Override) = метод в потомке с тем же именем, что и в родителе. Побеждает версия потомка.
super()= доступ к версии родителя на том жеself.- Потомок с собственным
__init__обязан вызватьsuper().__init__(...)(безself), а затем добавить только новое. - Расширение:
super().method()+ дополнение. Полное переопределение: безsuper(). self.method()внутри переопределённого метода = рекурсия. Для версии родителя —super().method().- Метод родителя, который вызывает
self.x(), автоматически дойдёт до переопределённой версии потомка. - Переопределённый метод сохраняет ту же сигнатуру (те же параметры).
Официальная документация
Практика
Упражнения опираются только на то, что пройдено в этом уроке и в предыдущих. Сначала попробуйте сами, и только потом открывайте решение.
Что вы отрабатываете на этой странице: наследование — родитель и потомок, тест «является разновидностью», расширение __init__ с помощью super() и переопределение метода (Override).
К концу страницы вы будете уметь:
- Писать потомка, который наследует родителя и добавляет только новое
- Решать, когда подходит наследование, с помощью теста «является разновидностью»
- Вызывать
super().__init__, чтобы родитель инициализировал свою часть - Переопределять метод и использовать внутри него версию родителя
Упражнение 1: Сотрудник и менеджер
Что вы строите: сотрудника и менеджера — где менеджер является разновидностью сотрудника, поэтому общее не пишется заново.
Что написать:
- Класс с именем
Employeeс именем и зарплатой. - В
Employee:describe, который возвращает имя и зарплату, как в выводе. - Класс с именем
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: Дерево животных
Что вы строите: маленькое дерево наследования — животное и два вида животных, у каждого своё действие.
Что написать:
- Класс с именем
Animalс именем и возрастом иdescribe, который возвращает имя и возраст, как в выводе. - Класс с именем
Dog, который наследуетAnimal, сfetch, который возвращает предложение из вывода. - Класс с именем
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 колёс, а ещё есть максимальный вес груза.
Что написать:
- Класс с именем
Vehicleс маркой и числом колёс. - Класс с именем
Truck, который наследуетVehicle. Его__init__получает марку и максимальный вес. - Внутри
__init__уTruck: вызовитеsuper().__init__с маркой и 6 колёсами и инициализируйте текущий вес нулём. 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) инициализирует часть родителя. После этого потомок добавляет собственные атрибуты.
Упражнение 5: Переопределение с super
Что вы строите: менеджера, у которого месячная стоимость — зарплата плюс бонус.
Что написать:
- Класс с именем
Employeeс именем и зарплатой иmonthly_cost, который возвращает зарплату. - Класс с именем
Manager, который наследуетEmployeeи получает ещё и бонус. В его__init__вызовитеsuper().__init__. - В
Managerпереопределитеmonthly_cost: версия родителя через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() вызывает версию родителя. Если расчёт базовой зарплаты изменится, Manager получит изменение автоматически.
Мини-проект: Сотрудники компании
Какую систему вы строите: систему сотрудников для компании. В компании есть обычные сотрудники, менеджеры и стажёры. Все они сотрудники — у них есть имя и зарплата, — но каждый вид отображается по-своему, а у менеджера есть ещё и бонус. Система отображает каждого сотрудника и считает месячную стоимость всех вместе.
Чему вы научитесь в проекте:
- Строить родителя со всем общим и потомков, которые добавляют только отличающееся
- Вызывать
super().__init__из потомка - Переопределять метод и расширять метод с помощью
super() - Видеть, как метод, написанный один раз в родителе, правильно работает для каждого вида
Что построить — шаг за шагом:
Шаг 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К концу проекта вы будете уметь:
- Проектировать иерархию: что поднимается в родителя, а что остаётся в потомке
- Различать три варианта для метода в потомке: унаследовать, переопределить или расширить с помощью
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 ищет метод сначала в потомке.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