Как работает порядок разрешения методов (MRO) в Python 2

Ответ

MRO (Method Resolution Order) определяет последовательность, в которой Python ищет метод в иерархии классов при множественном наследовании. В Python 2 существовало два разных подхода к MRO, зависящих от типа класса.

Важно: Поведение "классических классов" является устаревшим. В современном Python 3 все классы являются "новыми" и используют исключительно алгоритм C3.

1. Классические классы (Old-style classes)

Это классы, которые не наследуются от object. Они использовали простой алгоритм "сначала в глубину, затем слева направо" (depth-first, left-to-right). Этот подход мог приводить к проблемам при ромбовидном наследовании.

# Этот код корректно работает только в Python 2
class A:
    def who_am_i(self): print("I am A")

class B(A):
    pass

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

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

d = D()
d.who_am_i() # Вывод: I am A

Порядок поиска: D -> B -> A. Поиск доходит до A и останавливается, не проверяя класс C, хотя он ближе в иерархии.

2. Новые классы (New-style classes)

Это классы, которые явно или неявно наследуются от object. Для них используется более совершенный алгоритм C3-линеаризации. Он гарантирует, что дочерние классы всегда проверяются раньше родительских, решая проблему ромбовидного наследования.

# Этот код работает и в Python 2, и в Python 3
class A(object):
    def who_am_i(self): print("I am A")

class B(A):
    pass

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

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

d = D()
d.who_am_i() # Вывод: I am C

Порядок поиска (MRO): D -> B -> C -> A -> object. Поиск находит метод в C после проверки B и вызывает его.

Посмотреть MRO для класса можно с помощью атрибута __mro__ или метода .mro():

print(D.__mro__)
# (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)

Ответ 18+ 🔞

Ах ты ж ёпта, ну и тема у нас сегодня! MRO, блядь! Это ж та самая штука, которая решает, какого хуя твой метод будет найден, когда у тебя куча классов друг на друга наворочена. Как в той семье, где все друг другу родственники, и непонятно, кого слушать. Так вот, в Питоне это слушание и называется MRO.

Вот смотри, раньше, в старые добрые времена Python 2, была полная ёбаная неразбериха. Как два разных государства: классические классы и новые.

Классические классы (Old-style classes) Это как дедовский метод, наследуются они не от object, а просто так, с потолка. И алгоритм у них был, блядь, примитивный до безобразия: «сначала в глубину, потом слева направо». И вот эта простота, сука, приводила к пиздецу в ромбовидном наследовании.

# Запомни, это только для Python 2, а то сейчас начнёшь пробовать и охуеешь
class A:
    def who_am_i(self): print("I am A")

class B(A):
    pass

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

class D(B, C):
    pass

d = D()
d.who_am_i() # Вывод: I am A

Вот видишь, ебаный насос! Порядок поиска был: D -> B -> A. И всё! Нашёл метод в A и успокоился, как будто класса C с его переопределённым методом не существует в природе! Пиздец, да? Класс C сидит и думает: «Я тут, блядь, старался, переписывал, а меня в упор не видят!». Вот такие были времена, в рот меня чих-пых.

Новые классы (New-style classes) А потом пришёл светлый разум. Все классы стали наследоваться от object, явно или неявно. И для них придумали умный алгоритм — C3-линеаризацию. Он, сука, гарантирует, что дочерние классы всегда идут перед родительскими. Проблема ромбовидного наследования накрылась медным тазом.

# А вот это уже работает везде, и это правильно
class A(object):
    def who_am_i(self): print("I am A")

class B(A):
    pass

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

class D(B, C):
    pass

d = D()
d.who_am_i() # Вывод: I am C

Вот теперь порядок поиска (MRO) стал адекватным: D -> B -> C -> A -> object. Сначала проверил D, потом B, не нашёл, пошёл в C — о, ёба, нашёл! И вызвал его. Всё логично, всё справедливо. Красота, блядь.

И самое главное — ты можешь посмотреть эту цепочку наследования, как на параде. Есть два способа, оба рабочие:

print(D.__mro__)
# Или так:
print(D.mro())
# Выдаст тебе кортеж или список: (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)

Вот и вся магия, ёпта. Главное запомни: в Python 3 все классы — новые, и алгоритм C3 — наш царь и бог. А старые классические классы — это как история про то, как Герасим Муму утопил: вроде и жалко, но правильно сделал, что от них избавились.