Ответ
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 и законное право сказать: "Ну я же предупреждал, что так нельзя, блядь".