← Назад к списку
ТехническаяPythonSenior

Как работает 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__ в одном из классов и обрывают цепочку инициализации
  • Ставят миксины после базового класса, из-за чего их методы никогда не находятся первыми

ИП Кочкин Алексей Сергеевич · ИНН 390509026279 · ОГРНИП 325390000030973 · jiniys2005@yandex.ru