Как работает MRO при множественном наследовании и что на самом деле делает super()?
Короткий ответ
- MRO — порядок поиска атрибутов по иерархии классов
- Строится алгоритмом C3-линеаризации
- Посмотреть можно через Class.__mro__ или mro()
- super() идёт к следующему классу в MRO, а не к «родителю»
- Кооперативное наследование: каждый __init__ зовёт super()
- Противоречивая иерархия даёт TypeError при создании класса
MRO задаёт детерминированный порядок обхода классов по C3-линеаризации, а super() вызывает следующий класс именно в этом порядке, что делает миксины предсказуемыми.
Как сказать вслух
пример ответаMRO — это порядок, в котором Python ищет атрибуты и методы по иерархии классов; он строится алгоритмом C3 и сохраняет порядок перечисления родителей. Ключевой момент: super() вызывает не родителя, а следующий класс в MRO текущего объекта — поэтому в ромбовидном наследовании каждый класс вызывается ровно один раз. На этом строятся миксины: каждый метод делает свою часть и передаёт управление дальше по цепочке.
Подробный ответ
Основной ответ
При множественном наследовании Python линеаризует иерархию алгоритмом C3: класс идёт раньше своих родителей, порядок баз из определения сохраняется, и каждый класс встречается один раз. Результат доступен в Class.__mro__. Поиск атрибута идёт по этому списку до первого совпадения. super() возвращает прокси, который продолжает поиск с позиции после текущего класса в MRO экземпляра — поэтому в «ромбе» A-B-C-D цепочка super() обойдёт все классы по одному разу, без двойного вызова общего предка. Это называют кооперативным множественным наследованием: каждый метод вызывает super() и аккуратно передаёт **kwargs. Если C3 не может построить непротиворечивый порядок, создание класса падает с TypeError.
Ключевые моменты
- C3-линеаризация. Гарантирует монотонность: дочерний класс раньше родителей, локальный порядок баз сохраняется, обход детерминирован.
- super() — не «родитель». Это следующий класс в MRO типа экземпляра; в миксине super() может указать на класс, о котором миксин ничего не знает.
- Кооперативные __init__. Каждый конструктор принимает **kwargs и вызывает super().__init__(**kwargs), чтобы цепочка дошла до конца.
- Диагностика. Class.__mro__ и help(Class) показывают фактический порядок; это первый инструмент при отладке миксинов.
Практический контекст
На практике MRO всплывает в Django (миксины CBV: LoginRequiredMixin, PermissionRequiredMixin — их порядок важен), в DRF и в любых плагинных архитектурах. Интервьюер уровня senior ждёт объяснения, почему super() в ромбе не вызывает предка дважды, и как порядок миксинов меняет поведение. Хороший ответ включает пример с print в каждом классе и демонстрацию __mro__.
Пример кода
class A:
def hello(self):
print("A")
class B(A):
def hello(self):
print("B"); super().hello()
class C(A):
def hello(self):
print("C"); super().hello()
class D(B, C):
def hello(self):
print("D"); super().hello()
D().hello() # D B C A — каждый класс один раз
print([c.__name__ for c in D.__mro__]) # ['D','B','C','A','object']Частые ошибки
- Говорят «super() вызывает родительский класс» — в множественном наследовании это неверно
- Не вызывают super().__init__ в одном из классов и обрывают цепочку инициализации
- Ставят миксины после базового класса, из-за чего их методы никогда не находятся первыми