В чем различие между MRO в Python 2 и Python 3?

Ответ

MRO (Method Resolution Order) определяет порядок, в котором Python ищет методы и атрибуты в иерархии классов, особенно при множественном наследовании. Это критично для предсказуемого поведения объектов.

MRO в Python 2 (классический MRO):

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

MRO в Python 3 (C3 Linearization):

В Python 3 (и для новых классов в Python 2, наследующихся от object) был принят алгоритм C3 Linearization. Этот алгоритм гарантирует:

  1. Монотонность: Если класс C предшествует классу D в MRO класса X, то C также будет предшествовать D в MRO любого подкласса X.
  2. Сохранение локального порядка: Порядок базовых классов, указанный в определении класса, сохраняется.
  3. Сохранение порядка расширения: Если класс C является базовым для D, то C всегда будет предшествовать D в MRO.
  4. Обнаружение конфликтов: Если непротиворечивый MRO не может быть построен, Python выдаст ошибку TypeError.

C3 Linearization обеспечивает более предсказуемый, надежный и интуитивно понятный порядок разрешения методов, что делает множественное наследование более безопасным и управляемым.

Пример "проблемы ромба" и ее решения:

Рассмотрим следующую иерархию классов:

class A:
    def who_am_i(self): return "A"

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

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

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

# В Python 2 (для старых классов) MRO мог быть: D -> B -> A -> C -> A
# Это приводило к тому, что A вызывался дважды, и C мог быть проигнорирован
# или иметь неожиданный порядок относительно A.

# В Python 3 (C3 Linearization) MRO для D:
print(D.__mro__)
# Вывод: (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>,
#          <class '__main__.A'>, <class 'object'>)

# Это гарантирует, что каждый класс встречается ровно один раз
# и в логичном порядке, предотвращая неоднозначности.

obj_d = D()
print(obj_d.who_am_i())
# Вывод: B (поскольку B идет раньше C в MRO D)

Таким образом, переход к C3 Linearization в Python 3 значительно улучшил предсказуемость и надежность множественного наследования, устранив многие сложности и потенциальные ошибки, присущие старому алгоритму.