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

Как определяется порядок вызова методов при множественном наследовании в Python?

2.3 Middle🔥 71 комментариев
#Python Core#Архитектура и паттерны

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

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

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

Порядок вызова методов при множественном наследовании в Python (MRO)

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

Алгоритм C3 Linearization

Python использует алгоритм C3 Linearization (введён в Python 2.3) для определения MRO. Это гарантирует:

  • Порядок родителей сохраняется
  • Родитель обрабатывается только после всех его потомков
  • Каждый класс встречается ровно один раз

Просмотр MRO

Есть два способа увидеть порядок разрешения методов:

# Способ 1: метод __mro__
print(ClassName.__mro__)

# Способ 2: функция mro()
print(ClassName.mro())

# Способ 3: встроенная функция
help(ClassName)  # Показывает Method resolution order

Простой пример

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):
    pass

print(D.mro())  # [D, B, C, A, object]
print(D.__mro__)  # (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)

d = D()
d.method()  # Вывод: B

# Python ищет в порядке: D -> B -> C -> A -> object
# Находит в B, вызывает B.method()

Порядок поиска

При вызове метода Python ищет его в таком порядке:

  1. В самом классе
  2. В родительских классах (согласно MRO слева направо)
  3. В встроенном классе object

Более сложный пример

class A:
    def method(self):
        print("A")
    
    def show_mro(self):
        print(f"MRO: {self.__class__.mro()}")

class B(A):
    def method(self):
        print("B")

class C(A):
    def method(self):
        print("C")

class D(B, C):
    pass

class E(C, B):
    pass

print("D.mro():", D.mro())
# [D, B, C, A, object]

print("E.mro():", E.mro())
# [E, C, B, A, object]
# Порядок родителей в определении класса имеет значение!

D().method()  # Вывод: B
E().method()  # Вывод: C

Использование super()

super() использует MRO для вызова методов родительского класса:

class A:
    def method(self):
        print("A")

class B(A):
    def method(self):
        print("B")
        super().method()  # Вызывает следующий в MRO

class C(A):
    def method(self):
        print("C")
        super().method()  # Вызывает следующий в MRO

class D(B, C):
    def method(self):
        print("D")
        super().method()

print(D.mro())  # [D, B, C, A, object]

d = D()
d.method()
# Вывод:
# D
# B
# C
# A
# Каждый вызов super() переходит к следующему классу в MRO

Потенциальная проблема: Diamond Problem

Проблема алмаза возникает при вот таком наследовании:

#     A
#    / \\
#   B   C
#    \ /
#     D

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):
    pass

print(D.mro())  # [D, B, C, A, object]
# A вызывается только один раз, несмотря на два пути к нему

Без C3 алгоритма A был бы вызван дважды. С ним — ровно один раз.

Правила построения MRO

Python следует этим правилам:

  1. Порядок наследования слева направо — родители слева имеют приоритет
class D(B, C):  # B раньше C
    pass
  1. Родитель после всех потомков
class A: pass
class B(A): pass
class C(B): pass
# Порядок: C -> B -> A
  1. Монотонность — если X идёт перед Y в родительском классе, то X должен идти перед Y и в производном классе

Невозможные конфигурации

Пытаться создать конфигурацию, нарушающую правила MRO, приведёт к ошибке:

class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
class E(C, B):  # Это ок
    pass

# Но если создать:
class F(D, E):  # Ошибка!
    pass
# TypeError: Cannot create a consistent method resolution
# order (MRO) for bases B, C

Практический пример: Мixin классы

Mixin классы часто используются с множественным наследованием:

class TimestampMixin:
    def get_timestamp(self):
        from datetime import datetime
        return datetime.now().isoformat()

class LoggingMixin:
    def log(self, message):
        print(f"[LOG] {message}")

class User(TimestampMixin, LoggingMixin):
    def __init__(self, name):
        self.name = name
    
    def save(self):
        self.log(f"Сохранение пользователя {self.name}")
        print(f"Timestamp: {self.get_timestamp()}")

print(User.mro())
# [User, TimestampMixin, LoggingMixin, object]

user = User("Alice")
user.save()

Инспекция MRO во время разработки

import inspect

class MyClass(B, C):
    pass

# Получить MRO как список
mro = inspect.getmro(MyClass)
print(mro)

# Проверить, наследует ли класс от другого
print(issubclass(MyClass, B))  # True
print(issubclass(MyClass, A))  # True

# Проверить в runtime
obj = MyClass()
print(isinstance(obj, B))  # True
print(type(obj).__mro__)  # Полная цепь наследования

Общие ошибки

# ❌ Неправильно: вызов родителя напрямую
class B(A):
    def method(self):
        A.method(self)  # Плохо! Пропускает MRO

# ✅ Правильно: использование super()
class B(A):
    def method(self):
        super().method()  # Хорошо! Следует MRO

Заключение

MRO в Python:

  • Определяется алгоритмом C3 Linearization
  • Гарантирует корректный порядок вызова методов
  • Может быть просмотрен через ClassName.mro() или ClassName.__mro__
  • Работает слева направо согласно определению класса
  • super() автоматически следует MRO
  • Использование super() вместо явного вызова родителя критично для корректного наследования
Как определяется порядок вызова методов при множественном наследовании в Python? | PrepBro