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

В какую сторону реализован поиск аргумента в наследовании в Python

2.3 Middle🔥 171 комментариев
#Python Core

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

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

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

# Порядок поиска аргумента (атрибута) в наследовании Python

Краткий ответ

MRO (Method Resolution Order) — это порядок, в котором Python ищет методы и атрибуты в иерархии наследования. Реализован слева направо и глубина-поверхность-вперёд (C3 линеаризация).

Простой пример: линейное наследование

class A:
    value = "A"
    def method(self):
        print("метод A")

class B(A):
    value = "B"

class C(B):
    pass

obj = C()
print(obj.value)      # Вывод: "B"
print(obj.method())   # Вывод: метод A

# Порядок поиска для класса C:
# 1. C (сам класс)
# 2. B (родитель C)
# 3. A (родитель B)
# 4. object (корневой класс)

print(C.__mro__)
# (<class 'C'>, <class 'B'>, <class 'A'>, <class 'object'>)

Сложный пример: множественное наследование

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

obj = D()
obj.method()  # Вывод: B (не C!)

print(D.__mro__)
# D -> B -> C -> A -> object
# (слева направо: B перед C)

C3 линеаризация (алгоритм MRO)

Python использует C3 линеаризацию для вычисления MRO. Это гарантирует:

  1. Слева направо — в классе D(B, C) родители проверяются B → C → далее

  2. Глубина поверхность — сначала идём в глубину каждого пути

  3. Монотонность — если A было перед B где-то, то везде A перед B

# Пример сложной иерархии:

class A:
    pass

class B(A):
    pass

class C(A):
    pass

class D(B, C):
    pass

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

# Вычисление MRO для D:
# D + merge(
#     [B, C, A],      # D наследует от (B, C)
#     [B, A],         # B наследует от (A,)
#     [C, A],         # C наследует от (A,)
#     [B, C]          # порядок в определении D
# )
# → D
# → B (первый в списках, не блокирующий)
# → merge([C, A], [C, A], [C])
# → C (первый, не блокирующий)
# → merge([A], [A], [])
# → A (остаток)
# → object

Визуализация порядка поиска

# Диаграмма наследования:
#
#       object
#         |
#    ┌────┴────┐
#    |         |
#    A         A
#    |         |
#    B         C
#     \       /
#      \     /
#       \   /
#        \ /
#         D

# MRO для D: D → B → C → A → object
#
# Поиск идёт слева направо по диаграмме MRO:
# Атрибут? В D → нет
# Атрибут? В B → нет
# Атрибут? В C → нет
# Атрибут? В A → ДА!

Практические примеры

Пример 1: Поиск атрибута

class Animal:
    sound = "generic sound"

class Dog(Animal):
    sound = "woof"

class GoldenRetriever(Dog):
    pass

dog = GoldenRetriever()
print(dog.sound)  # Вывод: "woof"

# Порядок поиска:
# 1. GoldenRetriever.__dict__ (нет "sound")
# 2. Dog.__dict__ (ЕСТЬ "sound" = "woof")
# Результат: "woof"

Пример 2: Метод не определён в прямом классе

class Base:
    def work(self):
        return "base work"

class Child(Base):
    pass

obj = Child()
print(obj.work())  # Вывод: "base work"

# Порядок поиска:
# 1. Child.work() (нет)
# 2. Base.work() (ЕСТЬ)
# Результат: "base work"

Пример 3: super() использует MRO

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

class B(A):
    def greet(self):
        return "B -> " + super().greet()

class C(A):
    def greet(self):
        return "C -> " + super().greet()

class D(B, C):
    def greet(self):
        return "D -> " + super().greet()

obj = D()
print(obj.greet())  # Вывод: "D -> B -> C -> A"

# super() следует MRO:
# D.greet() вызывает super().greet()
# → следующий в MRO после D это B
# B.greet() вызывает super().greet()
# → следующий в MRO после B это C
# C.greet() вызывает super().greet()
# → следующий в MRO после C это A
# A.greet() возвращает "A"

Пример 4: Мероприятие в множественном наследовании

class Mixin1:
    def method(self):
        print("Mixin1")
        super().method()

class Mixin2:
    def method(self):
        print("Mixin2")
        super().method()

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

class Combined(Mixin1, Mixin2, Base):
    pass

obj = Combined()
obj.method()

# Вывод:
# Mixin1
# Mixin2
# Base

# MRO:
print(Combined.__mro__)
# (<class 'Combined'>, <class 'Mixin1'>, <class 'Mixin2'>,
#  <class 'Base'>, <class 'object'>)

Направление поиска (слева направо, вверх вниз)

Слева направо

class Parent1:
    value = "P1"

class Parent2:
    value = "P2"

class Child(Parent1, Parent2):
    pass

# Parent1 перед Parent2, поэтому:
print(Child().value)  # "P1"

# Поверни порядок:
class Child2(Parent2, Parent1):
    pass

print(Child2().value)  # "P2"

Вверх вверх (глубина в глубину)

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

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

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

class D(B, C):
    pass

# MRO: D → B → C → A
# Сначала идём в глубину по B → A
# Потом по C → A
# Результат: B перед C
print(D().method())  # "B"

Когда MRO может быть невалидна (ошибка)

# Это вызовет ошибку! (невозможно линеаризовать)
try:
    class A:
        pass
    
    class B(A):
        pass
    
    class C(A):
        pass
    
    class D(A, B):  # Конфликт! A перед B в определении
        pass       # но B наследует от A (должен быть B перед A)
except TypeError as e:
    print(f"Ошибка: {e}")
    # Вывод: "Cannot create a consistent method resolution"

Как проверить MRO на практике

class A:
    pass

class B(A):
    pass

class C(A):
    pass

class D(B, C):
    pass

# Способ 1: __mro__
print(D.__mro__)
# (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)

# Способ 2: mro()
print(D.mro())
# [<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>]

# Способ 3: help()
help(D)
# покажет MRO в начале

# Способ 4: inspect
import inspect
print(inspect.getmro(D))
# (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)

Правила для запоминания

  1. Слева направо — в class D(B, C) проверяем B перед C
  2. Глубина первой — идём в глубину иерархии B перед тем как проверить C
  3. Монотонна — если A перед B, то везде A перед B
  4. C3 линеаризация — алгоритм вычисления этого порядка
  5. super() её используетsuper().method() ищет в MRO

Вывод

Порядок поиска атрибутов в наследовании Python:

  • Слева направо — порядок родителей в определении класса
  • Глубина в глубину — сначала вся иерархия B, потом C
  • Реализовано через MRO (C3 линеаризация) — гарантирует консистентный и предсказуемый порядок
  • Проверяемо через .__mro__ или .mro()