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

«Почему в Python появилось правило MRO с введением базового класса `object`?» — вопрос из категории ООП, который задают на 10% собеседований Python Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В Python метод разрешения порядка (MRO) появился с введением object как универсального базового класса. Основная причина — необходимость надежно и предсказуемо разрешать конфликты при множественном наследовании, особенно в сценариях "ромбовидного наследования".

MRO определяет:

  • Порядок, в котором интерпретатор Python ищет методы и атрибуты в иерархии классов.
  • Гарантирует, что при вызове метода будет найдена корректная реализация, даже если она определена в нескольких родительских классах.

Для этого используется алгоритм C3 линеаризации, который обеспечивает:

  • Монотонность: Если класс C1 предшествует C2 в MRO класса X, то C1 всегда будет предшествовать C2 в MRO любого подкласса X.
  • Сохранение локального порядка: Порядок базовых классов, указанный при определении класса, сохраняется.
  • Сохранение порядка расширения: Подкласс всегда предшествует своим базовым классам.

Пример (проблема "ромбовидного наследования"):

class A:
    def method(self):
        print("Метод из A")

class B(A):
    def method(self):
        print("Метод из B")

class C(A):
    def method(self):
        print("Метод из C")

class D(B, C):
    # Если бы не было MRO, было бы неясно, какой метод (B или C) вызывать
    pass

d = D()
d.method()
# Вывод: Метод из B

# Порядок разрешения методов для класса D:
print(D.__mro__)
# Вывод: (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

В данном примере D.__mro__ явно показывает, что метод будет искаться сначала в D, затем в B, потом в C, и только потом в A и object. Без object как общего предка и строгого MRO, множественное наследование могло бы приводить к непредсказуемым и трудноотлаживаемым ошибкам.