Ответ вслух Senior ООП и dataclasses обновлено

MRO и super при множественном наследовании

Вопрос

Объясните, как Python ищет методы при множественном наследовании и как работает super().

Попробуйте сами

Ответьте вслух за 2 минуты, как на собесе. Потом сверьтесь с тезисами: что прозвучало, а что нет.

Показать ответ и разбор

Коротко

Образцовый ответ: «При множественном наследовании порядок поиска атрибутов задаёт MRO, его видно в D.mro. Строится он алгоритмом C3-линеаризации с двумя гарантиями: класс всегда раньше своих родителей, и порядок родителей из объявления сохраняется.

Что должно прозвучать в ответе

  • MRO строится C3-линеаризацией, видно в `Class.__mro__`

  • Потомок раньше предков, порядок родителей из объявления сохраняется

  • `super()` вызывает следующий класс в MRO объекта, а не прямого родителя

  • Кооперативное наследование: каждый метод вызывает `super()`, принимает `**kwargs`

  • Ромб: каждый метод выполняется один раз

  • Если линеаризация невозможна, `TypeError` при создании класса

Что могут спросить следом

  • Что такое миксины и где вы их видели?
  • Чем super().__init__() лучше Parent.__init__(self)?

Разбор

Образцовый ответ: «При множественном наследовании порядок поиска атрибутов задаёт MRO, его видно в D.__mro__. Строится он алгоритмом C3-линеаризации с двумя гарантиями: класс всегда раньше своих родителей, и порядок родителей из объявления сохраняется. Для ромба D(B, C), где оба наследуют A, получится D, B, C, A, object. Если такой порядок построить нельзя, например родители перечислены в противоречивом порядке, Python бросит TypeError прямо при создании класса. super() возвращает прокси, который ищет метод в MRO типа текущего объекта, начиная со следующего после текущего класса. Поэтому из B вызов super().save() уйдёт в C, а не в A, если объект класса D. Чтобы это работало, наследование должно быть кооперативным: каждый метод в цепочке вызывает super() и пропускает лишние аргументы через **kwargs. Тогда каждый метод выполнится ровно один раз. Если вместо этого звать A.__init__(self) явно, в ромбе A инициализируется дважды. На практике это нужно для миксинов, например в Django class-based views: LoginRequiredMixin ставят левее основного view».

Ответьте сами, а не прочитайте

В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.

Термины по теме