Method Resolution Order (MRO) — это механизм в Python, определяющий, в каком порядке классы будут просматриваться при поиске методов и атрибутов в случае множественного наследования.
C3-линеаризация (C3 linearization) — алгоритм, который используется для построения последовательности разрешения методов. Понимание MRO критически важно при проектировании сложной иерархии классов, чтобы избежать неоднозначностей и неожиданного поведения методов.
Узнать MRO для любого класса можно с помощью атрибута __mro__ или функции mro():
class A: def hello(self): print('A') class B(A): def hello(self): print('B') class C(A): def hello(self): print('C') class D(B, C): pass print(D.__mro__) # (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>) obj = D() obj.hello() # 'B', потому что B стоит раньше C в MRO
Менять MRO явно нельзя, но можно контролировать порядок перечисления базовых классов.
В каком порядке будут вызываться методы если класс наследует от нескольких родительских? Например:
class Base: def foo(self): print('Base') class Left(Base): def foo(self): print('Left') super().foo() class Right(Base): def foo(self): print('Right') super().foo() class Child(Left, Right): pass Child().foo()
Многие ожидают, что "foo" вызовется из Left, потом из Right, потом Base — так и есть. Однако если сменить порядок базовых классов (class Child(Right, Left)), порядок изменится!
Правильный ответ:
Порядок вызова будет таким, как определено в MRO. В первом случае — Left, Right, Base. MRO строится по определённому алгоритму (C3-линеаризация):
[Child, Left, Right, Base, object]
История
Множественное наследование логики, перекрытие метода
Команда разработчиков использовала множественное наследование для миксинов. Оказалось, что из-за неправильного порядка базовых классов, общая логика миксина была "перекрыта" дочерним классом, и часть бизнес-логики не исполнялась, что приводило к потере данных.
История
Вызывается не тот метод родителя
В финализирующем методе (__del__) ожидалось, что будет вызван метод базового класса. Однако, из-за неверного понимания MRO, был вызван другой, унаследованный метод миксина, где не происходила очистка ресурсов. В результате — утечка памяти.
История
Использование super() без понимания цепочки вызовов
В проекте Python 3 заказчик переписал часть кода с множественным наследованием, забыв обновить вызовы через super(). Это привело к ситуациями бесконечной рекурсии, когда метод всё время вызывал сам себя из-за ошибочного порядка разрешения методов.