Что такое MRO (Method Resolution Order) в Python

Ответ

MRO (Method Resolution Order) — это алгоритм, который определяет порядок поиска методов и атрибутов в иерархии классов при множественном наследовании в Python. Он создает предсказуемую, линеаризованную последовательность всех базовых классов.

Python использует алгоритм C3-линеаризации для построения MRO. Он гарантирует, что:

  • Дочерний класс всегда проверяется раньше родительского.
  • Если класс наследуется от нескольких родителей, их относительный порядок сохраняется.

Зачем это нужно? MRO решает проблему "алмаза" (diamond problem) и вносит ясность в сложных иерархиях наследования, делая поведение кода детерминированным.

Пример:

class A:
    def who_am_i(self):
        print("I am A")

class B(A):
    def who_am_i(self):
        print("I am B")

class C(A):
    def who_am_i(self):
        print("I am C")

# Порядок (B, C) важен при наследовании
class D(B, C):
    pass

d_instance = D()
d_instance.who_am_i() # Вывод: I am B

# Просмотр MRO для класса D
print(D.mro())
# Вывод:
# [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

Как видно из вывода D.mro(), Python сначала ищет метод в D, затем в B, затем в C и только потом в их общем предке A. Если Python не может построить согласованный MRO (например, из-за циклической зависимости), он вызовет исключение TypeError.

Ответ 18+ 🔞

А, ну это же про ту самую магию, когда у тебя один класс наследуется от кучи других, и все начинают орать "я первый!". MRO — это, по сути, официальный протокол драки за то, у кого из предков метод круче и чей вызовется.

Вот представь: есть дед A, у него два сына — B и C. А потом появляется внучок D, который нагло наследует сразу от обоих дядек. И все они написали метод who_am_i. Кого слушать-то, ёпта? Вот MRO и расставляет всех по полочкам, чтобы не было, как в плохом анекдоте, где "я твой отец, но ты меня не слушаешь".

Питон использует алгоритм C3-линеаризации — звучит, как болезнь какая-то, но на деле он просто строит чёткий список: "сначала спросим у самого младшего, потом у первого родителя, потом у второго, и так до дедушки object". Главные правила — ребёнок всегда важнее родителя, и порядок, в котором ты предков указал, сохраняется.

Смотри, вот живой пример, прям как в семье:

class A:
    def who_am_i(self):
        print("I am A")

class B(A):
    def who_am_i(self):
        print("I am B")

class C(A):
    def who_am_i(self):
        print("I am C")

# D — это тот самый наглый внук, который тянет одеяло на себя
class D(B, C):
    pass

d_instance = D()
d_instance.who_am_i() # Вывод: I am B — потому что B в очереди первый!

# А вот и сама очередь, можно посмотреть
print(D.mro())
# Вывод:
# [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

Видишь? Сначала сам D (мало ли, вдруг он тоже метод написал), потом B, потом C, потом общий предок A, и в конце уже object — царь и бог всех классов. Если бы B не имел своего метода, пошли бы спрашивать у C. А если бы и там было пусто — добрались бы до A. Всё честно, без подставы.

А если ты так криво наследование настроишь, что даже Питон не сможет понять, кто за кем, — получишь TypeError и законное право сказать: "Ну я же предупреждал, что так нельзя, блядь".