PythonMiddleTechnical

Что такое MRO?

MRO (Method Resolution Order) — линейный порядок классов, по которому Python ищет атрибут/метод. Доступен через Cls.__mro__ / Cls.mro(). Строится C3-линеаризацией: сохраняет local precedence order и monotonicity, при конфликте — TypeError при объявлении класса.

Что это

MRO — Method Resolution Order, линейный список классов, по которому Python ищет атрибут при obj.attr. Когда обращение не находит attribute в obj.__dict__, поиск идёт по type(obj).__mro__ слева направо. super() также делегирует следующему классу в MRO текущего экземпляра.

Доступ: Cls.__mro__ (tuple) или Cls.mro() (list). Всегда заканчивается на object.

Алгоритм C3

MRO вычисляется при создании класса по алгоритму C3 (Dylan, 1996; для Python — 2.3, new-style classes). Гарантии:

  • Local precedence order: порядок прямых родителей сохраняется. В class D(B, C) B идёт раньше C.
  • Monotonicity: если X идёт до Y в MRO предка, то и в MRO любого потомка.
  • Consistency: MRO потомка согласован с MRO каждого предка.

Если согласованного порядка нет — Python бросает TypeError: Cannot create a consistent method resolution order.

Пример ромба

class A:
    def ping(self):
        return "A"


class B(A):
    pass


class C(A):
    def ping(self):
        return "C"


class D(B, C):
    pass


print([cls.__name__ for cls in D.__mro__])
# ['D', 'B', 'C', 'A', 'object']

print(D().ping())  # 'C' — потому что C идёт раньше A в MRO


# Конфликт MRO — TypeError при объявлении
try:
    class X(B, A, C):
        pass
except TypeError as e:
    print(e)
    # Cannot create a consistent method resolution order (MRO) for bases A, C

super() и MRO

class A:
    def m(self):
        return "A"


class B(A):
    def m(self):
        return "B->" + super().m()


class C(A):
    def m(self):
        return "C->" + super().m()


class D(B, C):
    def m(self):
        return "D->" + super().m()


print(D.__mro__)
# (D, B, C, A, object)

print(D().m())  # D->B->C->A
# super() идёт по MRO, A вызывается ровно один раз (diamond решён)

Когда это важно

  • Кооперативные mixin-ы: каждый mixin вызывает super().__init__(**kwargs) и пробрасывает остальные kwargs дальше по MRO.
  • Django/DRF view, SQLAlchemy declarative mixins, pytest plugins — везде, где собирают функциональность.
  • Отладка «откуда взялся этот метод» — печатайте MRO и смотрите первый класс, где метод определён.

Подводные камни

  • Считать MRO просто DFS слева направо — это было в Python 2.2 (old-style); новый style использует C3, на ромбах порядок другой.
  • Конфликт MRO при объявлении: class D(B, A) когда B(A); решайте через корректный порядок.
  • Mixin не зовёт super().__init__() — цепочка обрывается, следующие в MRO не инициализируются.
  • Передача **kwargs в object.__init__ — он не принимает аргументов; последний mixin перед object должен «съесть» остатки.
  • Использовать super(ClsName, self) явно — устаревший стиль; в Py3 пишите просто super().
  • Изменять __bases__ на лету или динамически делать классы — MRO пересчитывается, читать такой код больно.

Common mistakes

  • Не знать mro.
  • Не объяснять ромбовидное наследование.
  • Путать MRO и порядок создания объектов.

What the interviewer is testing

  • Может прочитать mro.
  • Понимает связь с super.
  • Знает, что алгоритм — C3.

Sources

Related topics