Наследование, super() и MRO
Наследование
Класс-наследник получает все атрибуты и методы родителя и может добавлять новые и переопределять существующие:
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "..."
def describe(self):
return f"{self.name} говорит: {self.speak()}"
class Dog(Animal):
def speak(self): # переопределение
return "Гав!"
class Cat(Animal):
def speak(self):
return "Мяу"
def purr(self): # новый метод
return "Мррр"
for pet in [Dog("Шарик"), Cat("Мурка"), Animal("Некто")]:
print(pet.describe())
print(Cat("Мурка").purr())
describe написан один раз в Animal, но вызывает self.speak() — и для собаки срабатывает собачья версия. Это полиморфизм.
class Animal: pass
class Dog(Animal): pass
d = Dog()
print(isinstance(d, Dog), isinstance(d, Animal), isinstance(d, object))
print(issubclass(Dog, Animal), Dog.__bases__, Dog.__mro__)
Все классы неявно наследуются от object.
super()
super() вызывает реализацию из следующего класса в MRO — обычно это родитель:
class User:
def __init__(self, name, email):
self.name = name
self.email = email
def info(self):
return f"{self.name} <{self.email}>"
class Admin(User):
def __init__(self, name, email, permissions):
super().__init__(name, email) # родитель инициализирует свои поля
self.permissions = permissions
def info(self):
return super().info() + f" [админ: {', '.join(self.permissions)}]"
a = Admin("Анна", "anna@x.ru", ["delete", "ban"])
print(a.info())
Не забывайте super().init
Если наследник переопределяет __init__ и не вызывает родительский, атрибуты родителя не будут созданы → AttributeError при обращении к ним.
Множественное наследование и MRO
Класс может наследовать от нескольких родителей. Тогда важен порядок разрешения методов (Method Resolution Order):
class A:
def who(self): return "A"
class B(A):
def who(self): return "B"
class C(A):
def who(self): return "C"
class D(B, C):
pass
print(D().who())
print([cls.__name__ for cls in D.__mro__])
Python строит MRO алгоритмом C3-линеаризации. Правила:
- Наследник всегда раньше родителей.
- Порядок родителей из
class D(B, C)сохраняется. - Общий предок идёт после всех своих наследников — поэтому
AпослеC, а не сразу послеB.
Если такой порядок невозможен, Python откажется создавать класс (TypeError: Cannot create a consistent method resolution order).
Кооперативный super
super() — это «следующий в MRO экземпляра», а не «мой родитель». Благодаря этому каждый класс ромба выполняется ровно один раз:
class Base:
def __init__(self):
print("Base")
class Left(Base):
def __init__(self):
print("Left →")
super().__init__()
class Right(Base):
def __init__(self):
print("Right →")
super().__init__()
class Child(Left, Right):
def __init__(self):
print("Child →")
super().__init__()
Child()
print([c.__name__ for c in Child.__mro__])
super() внутри Left вызвал Right, хотя Left ничего не знает о Right. Чтобы это работало, все классы цепочки должны вызывать super() и принимать **kwargs, передавая их дальше.
Миксины
Миксин — небольшой класс с одной функцией, который «подмешивают» к другим через множественное наследование. Он не предназначен для самостоятельного использования:
import json
class ReprMixin:
def __repr__(self):
fields = ", ".join(f"{k}={v!r}" for k, v in vars(self).items())
return f"{type(self).__name__}({fields})"
class JsonMixin:
def to_json(self):
return json.dumps(vars(self), ensure_ascii=False)
class Order(ReprMixin, JsonMixin):
def __init__(self, id, total):
self.id, self.total = id, total
o = Order(1, 990)
print(o, o.to_json())
Миксины широко используются в Django: LoginRequiredMixin, PermissionRequiredMixin в представлениях на классах.
Композиция вместо наследования
Наследование — сильная связь: изменения родителя ломают наследников, глубокие иерархии трудно понимать. Часто лучше композиция: объект содержит другие объекты и делегирует им работу.
class Engine:
def start(self):
return "двигатель запущен"
class Radio:
def play(self):
return "играет музыка"
class Car: # машина ИМЕЕТ двигатель и радио
def __init__(self):
self.engine = Engine()
self.radio = Radio()
def drive(self):
return f"{self.engine.start()}, {self.radio.play()}, поехали!"
print(Car().drive())
| Отношение | Пример | Инструмент |
|---|---|---|
| «является» (is-a) | Собака — животное | наследование |
| «имеет» (has-a) | У машины есть двигатель | композиция |
Принцип
«Предпочитайте композицию наследованию» (Gang of Four). Наследуйте, когда наследник действительно является частным случаем родителя и может заменить его везде (принцип подстановки Лисков).
Итог
- Наследник получает атрибуты и методы родителя, может добавлять и переопределять.
super()вызывает следующий класс в MRO; не забывайтеsuper().__init__(...).- MRO строится C3-линеаризацией: наследник раньше родителей, порядок объявления сохраняется; смотреть —
Class.__mro__. - Миксины добавляют поведение через множественное наследование.
- Для «имеет» — композиция; для «является» — наследование.
Практика решено 0 из 5
Есть класс Employee. Создайте наследника Manager:
- __init__(self, name, salary, team) вызывает родительский __init__ через super() и сохраняет team (список);
- метод annual_bonus() переопределён: у менеджера бонус = родительский бонус + 5000 за каждого человека в команде.
Какие тесты проверяют код (4)
m = Manager("Анна", 200000, ["Боря", "Вера"]); assert m.name == "Анна" and m.team == ["Боря", "Вера"]
m = Manager("Анна", 200000, ["Боря", "Вера"]); assert m.annual_bonus() == 30000
assert isinstance(Manager("x", 1, []), Employee)
assert Employee("x", 1000).annual_bonus() == 100
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def annual_bonus(self):
return self.salary * 0.1
class Manager(Employee):
def __init__(self, name, salary, team):
super().__init__(name, salary)
self.team = team
def annual_bonus(self):
return super().annual_bonus() + 5000 * len(self.team)
Какой порядок MRO у класса D?
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
Что выведет код?
class A:
def hi(self): return "A"
class B(A):
def hi(self): return "B" + super().hi()
class C(A):
def hi(self): return "C" + super().hi()
class D(B, C):
def hi(self): return "D" + super().hi()
print(D().hi())
Напишите миксин JsonMixin с методом to_json(), который возвращает JSON-строку из атрибутов объекта
(vars(self)), с ensure_ascii=False и ключами по алфавиту (sort_keys=True).
Затем класс User(JsonMixin) с атрибутами name и age.
Какие тесты проверяют код (1)
assert User("Анна", 28).to_json() == '{"age": 28, "name": "Анна"}'
import json
class JsonMixin:
def to_json(self):
return json.dumps(vars(self), ensure_ascii=False, sort_keys=True)
class User(JsonMixin):
def __init__(self, name, age):
self.name = name
self.age = age
Когда лучше использовать композицию вместо наследования?
Войдите, чтобы отмечать пройденные уроки.