Python: уроки, справочник и задачи

Множественное и многоуровневое наследование в Python

Раздел: ООП в Python · Обновлено 31.10.2026

Множественное наследование в Python позволяет классу получить атрибуты и методы сразу от нескольких родителей. В статье разобраны синтаксис, отличие от многоуровневого наследования, порядок поиска методов MRO, ромбовидная схема и правильная работа super(). Отдельно показаны миксины, ради которых этот механизм чаще всего и используют.

Множественное и многоуровневое наследование в Python
Фото: WaleBadiora / CC BY-SA 4.0, Wikimedia Commons
Содержание статьи
  1. Синтаксис множественного наследования
  2. Многоуровневое наследование
  3. Порядок поиска методов MRO
  4. Ромбовидное наследование и super()
    1. Кооперативный __init__ с несколькими родителями
  5. Миксины: практическое применение
  6. Когда лучше обойтись без множественного наследования
  7. Частые вопросы
    1. Есть ли в Python интерфейсы, как в Java?
    2. Как узнать, от каких классов наследует класс?
    3. Можно ли вызвать метод конкретного родителя, минуя MRO?
    4. Можно ли наследовать от встроенных типов вместе с обычными классами?

Синтаксис множественного наследования

Родительские классы перечисляют через запятую в скобках после имени класса. Наследник получает все методы и атрибуты каждого родителя, а при совпадении имен побеждает тот родитель, который указан левее. Основы одиночного наследования описаны в статье про наследование классов, здесь речь о случае с несколькими предками.

class Flyer:
    def fly(self):
        return f"{self.name} летит"

    def move(self):
        return "по воздуху"

class Swimmer:
    def swim(self):
        return f"{self.name} плывет"

    def move(self):
        return "по воде"

class Duck(Flyer, Swimmer):
    def __init__(self, name):
        self.name = name

d = Duck("Утка")
print(d.fly())    # Утка летит
print(d.swim())   # Утка плывет
print(d.move())   # по воздуху: Flyer стоит первым

Проверка через isinstance(d, Swimmer) вернет True: объект считается экземпляром каждого из родителей. Это позволяет передавать утку в любую функцию, которая ждет летающий или плавающий объект.

Многоуровневое наследование

Многоуровневое наследование - это цепочка: класс наследует от наследника другого класса. Глубина цепочки не ограничена, а методы ищутся снизу вверх, от самого производного класса к базовому.

class Base:
    def info(self):
        return "Base"

class Derived1(Base):
    pass

class Derived2(Derived1):
    def info(self):
        return "Derived2 и " + super().info()

print(Derived2().info())  # Derived2 и Base

У Derived1 нет своего метода info(), поэтому super() из Derived2 дойдет до Base. Оба вида наследования легко сочетаются в одной иерархии.

ВидКак записываетсяСколько прямых родителей
Одиночноеclass B(A)Один
Многоуровневоеclass C(B), где B(A)Один, но у него свои предки
Множественноеclass D(B, C)Два и больше

Порядок поиска методов MRO

Когда у нескольких предков есть метод с одним именем, Python выбирает его по списку MRO (Method Resolution Order). Список строится алгоритмом C3-линеаризации и подчиняется двум правилам: потомок всегда идет раньше своих родителей, а родители идут в том порядке, в каком перечислены в скобках. Посмотреть список можно через атрибут __mro__ или метод mro().

print([cls.__name__ for cls in Duck.__mro__])
# ['Duck', 'Flyer', 'Swimmer', 'object']

Если порядок родителей противоречив и построить согласованный список нельзя, класс не создастся вовсе. Например, запись class X(Base, Derived1) вызовет TypeError: Cannot create a consistent method resolution order (MRO), потому что Base указан раньше своего же наследника.

Ромбовидное наследование и super()

Ромб возникает, когда два родителя класса наследуют от общего предка. Главный вопрос в такой схеме: сколько раз вызовется метод общего предка. Благодаря MRO и super() он вызывается ровно один раз.

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

class B(A):
    def hello(self):
        print("B")
        super().hello()

class C(A):
    def hello(self):
        print("C")
        super().hello()

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

D().hello()   # D B C A (каждое с новой строки)
print([k.__name__ for k in D.mro()])  # ['D', 'B', 'C', 'A', 'object']

Ключевой момент: super() вызывает не родителя текущего класса, а следующий класс в MRO того объекта, с которым работает метод. Внутри B.hello() для объекта D следующим идет C, а не A. Если бы B вызывал A.hello(self) напрямую по имени, метод C был бы пропущен.

Кооперативный __init__ с несколькими родителями

Чтобы инициализация прошла по всей цепочке, каждый класс должен вызывать super().__init__() и передавать дальше аргументы, которые ему не нужны. Для этого используют **kwargs: каждый класс забирает свои именованные аргументы, а остальное отдает следующему.

class Named:
    def __init__(self, name, **kwargs):
        super().__init__(**kwargs)
        self.name = name

class Aged:
    def __init__(self, age, **kwargs):
        super().__init__(**kwargs)
        self.age = age

class Person(Named, Aged):
    pass

p = Person(name="Оля", age=25)
print(p.name, p.age)  # Оля 25

Цепочка заканчивается на object.__init__(), который не принимает аргументов. Поэтому к этому моменту все kwargs должны быть разобраны, иначе вы получите TypeError.

Миксины: практическое применение

Миксин (mixin) - небольшой класс, который добавляет одну возможность и не предназначен для создания собственных объектов. Такие классы подмешивают к основному через множественное наследование. Подход широко используется в Django и стандартной библиотеке, например в socketserver.ThreadingMixIn.

import json

class ToJsonMixin:
    def to_json(self):
        return json.dumps(self.__dict__, ensure_ascii=False)

class ReprMixin:
    def __repr__(self):
        fields = ", ".join(f"{k}={v!r}" for k, v in self.__dict__.items())
        return f"{type(self).__name__}({fields})"

class User(ToJsonMixin, ReprMixin):
    def __init__(self, login, email):
        self.login = login
        self.email = email

u = User("admin", "a@example.com")
print(u.to_json())  # {"login": "admin", "email": "a@example.com"}
print(u)            # User(login='admin', email='a@example.com')

Миксины принято ставить левее основного базового класса: class MyView(LoginRequiredMixin, View). Тогда их методы перекрывают методы базы и могут вызвать ее реализацию через super(). Подробнее о методах вроде __repr__ рассказано в статье про магические методы.

Когда лучше обойтись без множественного наследования

Если классы-родители большие, со своим состоянием и пересекающимися именами атрибутов, иерархия быстро становится запутанной. Признак проблемы: чтобы понять поведение метода, нужно выписывать MRO. В таких случаях чаще подходит композиция, когда объект хранит другой объект в атрибуте и вызывает его методы. Отношение "имеет" (у машины есть двигатель) выражают композицией, а "является" (утка является птицей) наследованием. Общие принципы ООП в Python собраны в статье ООП Python, а формальное описание алгоритма есть в документации Python.

Частые вопросы

Ответы на вопросы, которые чаще всего возникают при изучении темы.

Есть ли в Python интерфейсы, как в Java?

Отдельного ключевого слова нет. Их роль играют абстрактные классы из модуля abc и протоколы typing.Protocol. Наследовать от нескольких абстрактных классов можно так же, как от обычных.

Как узнать, от каких классов наследует класс?

Атрибут __bases__ содержит кортеж прямых родителей, а __mro__ полный порядок поиска. Для проверки отношения есть функция issubclass(Duck, Flyer).

Можно ли вызвать метод конкретного родителя, минуя MRO?

Да, явным вызовом Swimmer.move(self). Это допустимо в простых случаях, но в ромбовидных иерархиях может привести к повторным вызовам общего предка.

Можно ли наследовать от встроенных типов вместе с обычными классами?

Можно, например class MyDict(ReprMixin, dict). Ограничение одно: нельзя наследовать сразу от двух встроенных типов с несовместимым устройством в памяти, например от list и dict, Python выдаст TypeError.