Как изменить поведение метода родительского класса при наследовании в Python
Что проверяют
Знаете ли вы разницу между полной заменой метода и его расширением и понимаете ли, как super() ведёт себя при множественном наследовании. Типичный уточняющий вопрос: что такое MRO и в каком порядке вызовутся методы в ромбе.
Как отвечать
Базовый механизм: объявляете в наследнике метод с тем же именем, и поиск атрибута находит его раньше родительского. Дальше варианты:
- полная замена: метод наследника не зовёт родителя, поведение другое целиком;
- расширение: внутри вызываете
super().method(*args, **kwargs)и добавляете логику до или после. Так почти всегда пишут__init__, иначе атрибуты родителя не появятся; - шаблонный метод: родитель задаёт алгоритм и вызывает «крючки» вроде
_validate(), а наследник переопределяет только их. Для обязательных крючков естьabc.ABCи@abstractmethod, экземпляр класса без реализации просто не создастся; typing.override: декоратор из Python 3.12, на выполнение не влияет, но mypy и pyright поймают опечатку в имени переопределяемого метода.
Про множественное наследование расскажите отдельно. super() вызывает не «родителя», а следующий класс в MRO, который строится C3-линеаризацией, посмотреть его можно через Class.__mro__. Поэтому в ромбе каждый класс должен звать super() и пропускать дальше лишние аргументы через **kwargs, тогда общий предок вызовется ровно один раз. super() без аргументов в Python 3 работает за счёт ячейки __class__, которую компилятор создаёт для методов, объявленных внутри класса.
Сигнатуру переопределённого метода держите совместимой с родительской: код, работающий с базовым типом, не должен ломаться на наследнике. Это принцип подстановки Лисков.
Где спотыкаются
Вызывают родителя напрямую, Parent.method(self). В одиночном наследовании это работает, а в ромбе общий предок выполнится дважды или цепочка оборвётся.
Забывают super().__init__() в наследнике и потом ловят AttributeError на поле, которое должен был создать родитель.
Пытаются переопределить метод с двумя подчёркиваниями, __helper. Из-за name mangling он превращается в _Parent__helper, и версию наследника родитель не вызовет никогда.