← Назад к вопросам

Какая причина изменений в Python в механизме MRO?

2.0 Middle🔥 141 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

История и причины изменения MRO (Method Resolution Order) в Python

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

Python 2.1 и ранее: Depth-First Search (DFS)

В ранних версиях Python использовался простой алгоритм DFS (поиск в глубину).

# Python 2.1 — классы old-style
class A:
    def method(self):
        return "A"

class B(A):
    pass

class C(A):
    pass

class D(B, C):
    pass

# MRO в Python 2.1: D -> B -> A -> C -> A (DFS слева направо)
# Проблема: A повторяется! И это неправильно — C.method() никогда не будет вызван

Проблема с DFS: При множественном наследовании порядок неправильный — один класс может быть посещён несколько раз, и правые базовые классы могут быть пропущены.

Python 2.3: C3 Linearization (Merge Algorithm)

В Python 2.3 был введён алгоритм C3 Linearization, который решил проблемы DFS. Это осталось стандартом и в Python 3.

# Python 2.3+ — классы new-style (наследуют от object)
class A:
    def method(self):
        return "A"

class B(A):
    def method(self):
        return "B"

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

class D(B, C):
    pass

obj = D()
print(obj.method())  # "B" — потому что B идёт перед C в наследовании D

# MRO в Python 2.3+: [D, B, C, A, object]
print(D.__mro__)  # Показывает правильный порядок

Почему C3 Linearization лучше?

  1. Сохраняет порядок наследования — если D(B, C), то B ищется раньше C
  2. Монотонность — если A предшествует B в MRO, то A должна предшествовать B в MRO любого подкласса
  3. Каждый класс встречается только один раз
  4. Гарантирует правильный порядок вызова методов

Алгоритм C3: как это работает

# Пример сложного наследования
class O:
    pass

class A(O):
    pass

class B(O):
    pass

class C(O):
    pass

class D(A, B):
    pass

class E(B, C):
    pass

class F(D, E):
    pass

print(F.__mro__)
# [F, D, A, E, B, C, O, object]

# Алгоритм C3 решает: взять из всех списков первый элемент,
# который не встречается в хвостах других списков
# (хвост = всё кроме первого элемента)

# L[F] = F + merge(L[D], L[E], [D, E])
# L[D] = D + merge(L[A], L[B], [A, B])
# L[A] = A + merge(L[O], [O]) = [A, O]
# L[B] = B + merge(L[O], [O]) = [B, O]
# L[D] = [D, A, B, O]
# L[E] = [E, B, C, O]
# L[F] = [F, D, A, E, B, C, O, object]

Причины изменения механизма

1. DFS был непредсказуем

# Проблема DFS: duplicate bases
class A:
    pass

class B(A):
    pass

class C(A):
    pass

class D(B, C):
    pass

# Python 2.1 (DFS): [D, B, A, C, A]  <- A дважды!
# Это вызывает ошибку: TypeError: duplicate base class

# Python 2.3+ (C3): [D, B, C, A, object]  <- A один раз

2. Монотонность нарушалась

# В DFS порядок базовых классов мог изменяться
class A:
    pass

class B(A):
    pass

class C(A):
    pass

class D(A):
    pass

class E(D, B, C):
    pass

# Python 2.1 DFS приводил к нелогичному порядку
# Python 2.3 C3 гарантирует: если D идёт перед B в наследовании E,
# то D будет раньше B в MRO

3. super() требует предсказуемого порядка

# super() был введён позже, но он требует правильного MRO
class A:
    def method(self):
        print("A.method")

class B(A):
    def method(self):
        print("B.method")
        super().method()  # Зависит от правильного MRO!

class C(A):
    def method(self):
        print("C.method")
        super().method()

class D(B, C):
    pass

obj = D()
obj.method()
# Выводит: B.method -> C.method -> A.method
# Правильный порядок возможен только с C3!

Практические примеры проблем без C3

Проблема 1: Вызов методов родителей

# Без правильного MRO super() работает неправильно
class Logger:
    def log(self, message):
        print(f"[LOG] {message}")

class Database:
    def log(self, message):
        print(f"[DB] {message}")

class Application(Logger, Database):
    def process(self):
        self.log("Processing...")  # Вызовет Logger.log
        super().log("Done")  # Благодаря C3 вызовет Database.log

app = Application()
app.process()
# [LOG] Processing...
# [DB] Done

print(Application.__mro__)
# [Application, Logger, Database, object]

Проблема 2: Diamond Inheritance

class Animal:
    def speak(self):
        return "Animal sound"

class Dog(Animal):
    def speak(self):
        return "Woof! " + super().speak()

class Cat(Animal):
    def speak(self):
        return "Meow! " + super().speak()

class Pet(Dog, Cat):
    pass

pet = Pet()
print(pet.speak())
# "Woof! Meow! Animal sound"

print(Pet.__mro__)
# [Pet, Dog, Cat, Animal, object]

# Без C3 могло быть:
# - Animal был бы вызван дважды
# - Порядок был бы непредсказуем
# - super().speak() в Dog не знал бы, куда идти дальше

Правило C3 в действии

# Сложный пример
class A:
    pass

class B(A):
    pass

class C:
    pass

class D(B, C):
    pass

class E(C):
    pass

class F(D, E):
    pass

print(F.__mro__)
# [F, D, B, E, C, A, object]

# Почему D перед E?
# - D перед E в наследовании F(D, E)
# - Поэтому все D-потомки идут перед E-потомками

# Почему B перед E?
# - B из D, а D раньше E

# Почему E перед C?
# - Хотя C в обоих (B->A), правило монотонности требует
# - E (подкласс C) идёт перед C

# Почему C перед A?
# - C встречается раньше A в списке

Когда можно столкнуться с проблемами MRO

# Плохая практика: циклическое наследование (ошибка)
# class A(B):
#     pass
# class B(A):  # TypeError!
#     pass

# Хорошая практика: избегать глубокого множественного наследования
class Base:
    pass

class Mixin1(Base):
    def method1(self):
        return "mixin1"

class Mixin2(Base):
    def method2(self):
        return "mixin2"

class Combined(Mixin1, Mixin2):
    pass

print(Combined.__mro__)
# [Combined, Mixin1, Mixin2, Base, object]

# Проверяй MRO перед написанием сложного наследования!

Итоги

Причины изменения MRO:

  1. DFS был невозможен для множественного наследования — приводил к ошибкам и дублированиям
  2. Нужен был предсказуемый порядок — для правильной работы super()
  3. Требовалась монотонность — если A перед B в родителе, должно быть перед B везде
  4. C3 Linearization решил все эти проблемы — это стало стандартом в Python 2.3 и остаётся в Python 3

Мудрость: избегай сложного множественного наследования. Если используешь его, всегда проверяй __mro__ и понимаешь, в каком порядке будут вызваны методы.

Какая причина изменений в Python в механизме MRO? | PrepBro