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

Почему с появлением базового класса object появилось правило MRO?

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

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

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

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

Появление MRO с базовым классом object

Возникновение C3 Linearization (правило MRO) в Python 2.3 напрямую связано с введением единого базового класса object для всех классов. Давайте разберём историческую необходимость этого.

История: От старых классов к новым

Python 2.0-2.2: Старые классы (без object)

# Python 2.0 - 2.2: Старые классы
class A:
    pass

class B(A):
    pass

print(type(A))  # <type 'classobj'>
print(isinstance(A, object))  # False! Не наследуется от object

В этом мире:

  • Классы были отдельной системой
  • Встроенные типы (int, str, dict) не были классами
  • Множественное наследование использовало Depth-First Search (DFS)
  • Было разделение между типами и классами

Python 2.2: Введение нового класса (с object)

# Python 2.2: Новое введение
class A(object):  # Явное наследование от object
    pass

class B(A):
    pass

print(type(A))  # <type 'type'>
print(isinstance(A, object))  # True! Все классы наследуются от object

Теперь все классы наследуются от одного общего базового класса object.

Почему DFS сломалась с появлением object

Проблема 1: Diamond Problem с object

Сразу после введения object, возникла глобальная проблема:

class A(object):
    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

# Какой будет MRO при DFS?
# D -> B -> A -> object -> C -> A -> object
#           ^ A дважды!
#                      ^ object дважды!

# Это просто неработающе!

Проблема: каждый класс теперь наследуется от object, поэтому возникают циклические зависимости.

Проблема 2: Super() не может работать с DFS

Python 2.2 также ввёл механизм super(), который требует консистентного порядка:

class A(object):
    def __init__(self):
        print("A.__init__")
        super().__init__()  # Вызов следующего в MRO

class B(A):
    def __init__(self):
        print("B.__init__")
        super().__init__()  # Должен вызвать C, а не A!

class C(A):
    def __init__(self):
        print("C.__init__")
        super().__init__()  # Должен вызвать object

class D(B, C):
    pass

d = D()

# DFS: D -> B -> A -> C -> object
# B.super() вызовет A, а не C! НЕПРАВИЛЬНО!

# C3: D -> B -> C -> A -> object
# B.super() вызовет C, затем C.super() вызовет A! ПРАВИЛЬНО!

С DFS super() работал бы неправильно.

Почему именно C3 Linearization

Свойство C3

C3 Linearization гарантирует:

  1. Консистентность

    • Каждый класс встречается ровно один раз
    • Нет дублирования
  2. Локальность (Local Precedence Order)

    • Порядок родителей в определении класса сохраняется
    • D(B, C) означает B перед C
  3. Монотонность (Monotonicity)

    • Если A перед B в MRO какого-то класса, то A будет перед B везде
# Математически:
# MRO(D) = D + merge(MRO(B), MRO(C), [B, C])
#
# MRO(B) = B + A + object
# MRO(C) = C + A + object
# [B, C] = порядок наследования
#
# Merge алгоритм:
# 1. Берём первый элемент первого списка
# 2. Если он не в хвосте других списков, добавляем
# 3. Если в хвосте - пропускаем, берём следующий
# 4. Повторяем до конца

Как работает алгоритм C3 на примере

class A(object):
    pass

class B(A):
    pass

class C(A):
    pass

class D(B, C):
    pass

# Шаг 1: Вычислим MRO(B) и MRO(C)
print(B.__mro__)  # (B, A, object)
print(C.__mro__)  # (C, A, object)

# Шаг 2: Применим алгоритм merge для D(B, C)
# merge(
#   [B, A, object],     # MRO(B)
#   [C, A, object],     # MRO(C)
#   [B, C]              # порядок наследования
# )

# Итерация 1:
#   Голова первого списка: B
#   B в хвостах других? Нет -> добавляем B
#   Удаляем B из всех списков
#   Осталось: merge([A, object], [C, A, object], [C])

# Итерация 2:
#   Голова первого списка: A
#   A в хвостах других? ДА! (в [C, A, object] A в хвосте)
#   Пропускаем A, берём голову второго списка: C
#   C в хвостах других? Нет -> добавляем C
#   Удаляем C, осталось: merge([A, object], [A, object])

# Итерация 3:
#   Голова: A
#   A в хвостах? Нет -> добавляем A
#   Осталось: merge([object], [object])

# Итерация 4:
#   Голова: object
#   Добавляем object

# Результат:
print(D.__mro__)  # (D, B, C, A, object)

Объект должен быть в конце MRO

Критически важно, чтобы object был в конце:

# С DFS это не гарантировалось:
# D -> B -> A -> object -> C -> A -> object
#                                      ^ конец, но A дублирован

# С C3 это гарантируется:
# D -> B -> C -> A -> object
#                     ^ единственный object в конце

Без object не было бы проблемы

В старых классах (без object) проблемы не было:

# Python 2.1: Старые классы
class A:
    pass

class B(A):
    pass

class C(A):
    pass

class D(B, C):
    pass

print(D.__mro__)  # DFS: (D, B, A, C)
# Работает! Потому что нет общего базового класса для всех

Но как только добавили object для всех классов, возникла проблема "бриллиант" (Diamond Problem).

Временная шкала

Python 2.0 (2000): Просто старые классы, DFS работает
   |
   v
Python 2.2 (2001): Введены новые классы с object базовым классом
   |
   v
Проблема: DFS не работает, super() сломан, множественное наследование неправильное
   |
   v
Rешение: Гвидо ван Россум принял C3 Linearization из CLOS (Common Lisp Object System)
   |
   v
Python 2.3 (2003): C3 становится стандартом для new-style классов
   |
   v
Python 3.0 (2008): C3 для ВСЕХ классов (old-style уничтожены)

Альтернативные решения, которые рассматривались

  1. DFS с дублированием (было в старых классах)

    • ❌ Неправильное поведение
    • ❌ super() сломан
  2. Right-to-left DFS (других языков)

    • ❌ Непредсказуемо
    • ❌ Нарушает локальность
  3. Запретить множественное наследование

    • ❌ Ограничение языка
    • ❌ Иногда нужно
  4. C3 Linearization

    • ✅ Консистентно
    • ✅ Предсказуемо
    • ✅ Правильно для super()
    • ✅ Уже доказано в Lisp

Вывод

MRO (C3 Linearization) появилось именно потому, что:

  1. Object стал базовым классом для всех → Diamond Problem
  2. Нужен был super() → требует консистентный порядок
  3. DFS был неправильным → давал циклические зависимости
  4. C3 решает все эти проблемы → консистентный, предсказуемый порядок

Без введения universal object класса, необходимость в C3 просто не возникла бы.