MRO в Python: почему D().f() печатает 'B'

MRO в Python: почему D().f() печатает 'B'

@python_quiz

Разберем этот квиз

Коротко: анализируем, как работает динамическое создание классов через type(), как Python разрешает множественное наследование и почему метод f для экземпляра D берётся из класса B.

Условие (код)

Вот исходный код, который разбираем:

python

Коротко: создаются четыре класса динамически. D наследует от B и C; B и C — от A. Метод f определён в A, B и C.

Что делает type(...) здесь

  • type(name, bases, dict) — динамически создаёт класс с именем name, базовыми классами bases и атрибутами из dict.
  • lambda s: "A" — это метод, ожидающий аргумент (обычно self), возвращает строку с буквой класса.

Эквивалентный код в декларативном синтаксисе:

python

Поведение идентично.

Разрешение методов: MRO (Method Resolution Order)

При вызове метода для экземпляра Python ищет его в порядке, определённом MRO класса. В современных версиях Python используется алгоритм C3-линеаризации, который выстраивает единственный корректный линейный порядок обхода базовых классов при множественном наследовании.

Для нашего случая порядок будет таким:
D → B → C → A → object

Можно убедиться программно:

python

Вывод будет примерно:
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

Это означает: при поиске атрибута/метода Python будет смотреть сначала в D, затем в B, затем в C, затем в A, затем в object.

Почему печатается 'B'

  • В D самого метода f нет.
  • Первый класс в MRO после D — B. В B определён f, поэтому Python берёт этот метод и вызывает его.
  • Метод B.f возвращает строку "B", поэтому печатается B.

Иными словами: метод найден в B по порядку MRO до того, как проверится C или A.

Анализ вариантов (коротко)

  • "Выведет 'A'": неверно — A находится ниже в MRO, к нему дойдёт только если B и C не содержат f.
  • "Выведет 'B'": соответствует MRO — верно, потому что B идёт раньше C и A.
  • "Выведет 'C'": неверно — C идёт после B в MRO, поэтому его f не используется.
  • "Ошибка MRO при создании класса D": неверно — MRO существует (C3 применим) и конфликтов нет, так как наследование B и C от общего A согласовано.

Демонстрация и проверка

Полный пример с выводом MRO и результата:

python

Ожидаемый вывод:
MRO: (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
D().f() -> B

Вывод

При множественном наследовании важно помнить про C3-линеаризацию: порядок поиска метода определяется MRO, а не просто списком базовых классов слева направо. В данном примере метод f взят из B потому, что B идёт раньше C и A в MRO класса D.

Report Page