Наследование, super() и переопределение методов
Содержание курса
В прошлом уроке мы разбирали композицию: один объект хранит другой как атрибут и делегирует ему работу. Это отношение «имеет» — has-a. Теперь переходим к другому механизму связи классов: наследованию, которое выражает отношение «является» — is-a. Это принципиально другая идея, и перепутать их — одна из самых частых ошибок в проектировании.
Базовый класс и подкласс: механика наследования
Наследование объявляется в одну строку — имя родителя указывается в скобках при определении класса:
class Animal:
def __init__(self, name: str) -> None:
self.name = name
def speak(self) -> str:
return "..."
class Dog(Animal):
pass
Dog здесь ничего не определяет сам. Но это уже полноценный класс: он автоматически получает __init__ и speak от Animal. Можно создать экземпляр и вызвать метод без какого-либо дополнительного кода:
dog = Dog("Rex")
print(dog.name) # Rex
print(dog.speak()) # ...
Python ищет атрибут или метод сначала в самом объекте, потом в его классе (Dog), потом поднимается по цепочке родителей. Это и есть MRO — Method Resolution Order. В простом случае с одним родителем цепочка линейная: Dog → Animal → object.
Проверить, является ли объект экземпляром родительского класса, можно через isinstance:
print(isinstance(dog, Dog)) # True
print(isinstance(dog, Animal)) # True
Оба вызова возвращают True — потому что Dog действительно является разновидностью Animal. Именно это слово «является» — ключевое.
Критерий is-a. Наследование имеет смысл только тогда, когда можно честно сказать: «подкласс является разновидностью базового класса». Dog является животным — это реальное is-a отношение. Но если между классами связь другого рода — один использует другой, один хранит другой — наследование здесь не на месте.
Проверка простая: подставьте оба класса в фразу «X является Y». Если фраза звучит нелепо или натянуто — скорее всего, нужна композиция, а не наследование. Например, «Движок является Автомобилем» — нет. «Автомобиль имеет Движок» — да. Значит, Engine должен быть атрибутом Car, а не его родителем.
Этот критерий — не философия, а практический фильтр. Неправильно применённое наследование создаёт классы, которые трудно менять: изменение родителя ломает всех детей, а замена поведения требует переписывать цепочку. Когда is-a соблюдается — наследование работает на вас. Когда нет — оно становится источником хрупкости.
