11.1 Method Resolution Order
Порядок разрешения методов (Method Resolution Order, MRO) определяет последовательность, в которой Python ищет методы и атрибуты в иерархии классов. Это особенно важно при работе с множественным наследованием, когда класс может наследовать атрибуты и методы от нескольких родительских классов.
Грубо говоря, существует строгий фиксированный порядок (или, скорее, алгоритм), согласно которому Python обходит дерево наследования классов. Этот алгоритм обеспечивает корректный порядок поиска методов, который можно описать следующим образом:
Алгоритм C3-линеаризации
Алгоритм C3-линеаризации определяет MRO путем комбинирования:
- Самого класса.
- Списка родительских классов в порядке их перечисления.
- MRO родительских классов в том же порядке.
Правила алгоритма C3-линеаризации
- Сохранять локальный порядок методов: если класс
Aуказан перед классомB, все методы классаAдолжны рассматриваться перед методами классаB. - Поддерживать порядок в родительских классах: если класс
Aявляется родителем классаB, то все методы классаAдолжны рассматриваться перед методами классаB. - Учитывать порядок наследования: если класс
Cявляется родителем для двух или более классов, порядок методов классаCдолжен сохраняться в MRO всех этих классов.
Шаги алгоритма:
Шаг 1. Начнем с самого класса:
Всегда начинаем с самого класса, в котором вызван метод.
Шаг 2. Добавляем базовые классы в порядке их перечисления:
После текущего класса проверяем базовые классы в том порядке, в каком они указаны при наследовании.
Шаг 3. Обходим родительские классы:
Ищем поля и методы там.
Шаг 4. Объединяем MRO родительских классов:
Если один и тот же базовый класс наследуется через несколько путей, он проверяется только один раз и в правильном порядке (все остальные разы он будет пропущен).
Для тех, кто уже знаком с темой «Алгоритмы и структуры данных», это поиск в глубину, а не в ширину.
11.2 Проверка MRO
В Python можно проверить порядок обхода методов и полей класса, используя атрибут __mro__ или функцию mro().
Пример:
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):
def method(self):
print("D")
# Проверка MRO
print(D.__mro__)
Вывод будет:
(<class '__main__.D'>,
<class '__main__.B'>,
<class '__main__.C'>,
<class '__main__.A'>,
<class 'object'>)
Это показывает порядок, в котором Python будет искать методы и атрибуты:
D: Python сначала проверяет метод в классеD.-
B: Затем Python проверяет метод в классеB(первый родительский класс). -
C: Если метод не найден в классеB, Python проверяет метод в классеC(второй родительский класс). -
A: Если метод не найден в классахBиC, Python проверяет метод в классеA. -
object: В конце концов, Python проверяет метод в базовом классеobject.
11.3 Использование super() с MRO
Функция super() следует MRO для вызова методов родительских классов в правильном порядке. Рассмотрим пример использования
super():
class A:
def method(self):
print("A")
super().method()
class B(A):
def method(self):
print("B")
super().method()
class C(A):
def method(self):
print("C")
super().method()
class D(B, C):
def method(self):
print("D")
super().method()
d = D()
d.method()
Вывод будет следующим:
D
B
C
A
Порядок обхода (MRO)
1. Вызов метода method класса D:
- Python сначала проверяет метод в классе
Dи находит его там. - Метод
D.method()выполняется и печатает"D". - Затем вызывается
super().method(), который следует MRO для вызова следующего метода.
2. Вызов метода method класса B:
- Согласно MRO, следующий класс после
D— этоB. - Метод
B.method()выполняется и печатает"B". - Затем вызывается
super().method(), который следует MRO для вызова следующего метода.
3. Вызов метода method класса C:
- Следующий класс в MRO после
B— этоC. - Метод
C.method()выполняется и печатает"C". - Затем вызывается
super().method(), который следует MRO для вызова следующего метода.
4. Вызов метода method класса A:
- Следующий класс в MRO после
C— этоA. - Метод
A.method()выполняется и печатает"A". - Затем вызывается
super().method(), но так как уAнет родительских методовmethod(кромеobject), вызов завершится без дальнейших действий.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ