Множественное наследование и миксины
Python разрешает классу нескольких родителей — и сразу отвечает на вопрос «чей метод наследовать» через линейный порядок MRO. Разберём миксины как безопасный способ множественного наследования, алмазную проблему и кооперативный super().
Редакция Питоники
Наследование от одного родителя вы уже знаете из урока 7. Python идёт дальше: класс может перечислить несколько родителей — class Book(JSONMixin, ReprMixin). В чистом виде это быстрая дорога к путанице «чей метод победил», поэтому на практике множественное наследование применяют в особом стиле: через миксины — классы-добавки поведения без своего состояния. Соберём пару таких миксинов и посмотрим, как Python разрешает конфликты имён.
Миксин: добавка поведения
import json
class JSONMixin:
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 Book(JSONMixin, ReprMixin):
def __init__(self, title, author):
self.title = title
self.author = author
b = Book("Война и мир", "Толстой")
print(b)
print(b.to_json())
Book(title='Война и мир', author='Толстой')
{"title": "Война и мир", "author": "Толстой"}Алмазная проблема и порядок MRO
class Base:
def greet(self):
return "привет от Base"
class Left(Base):
def greet(self):
return "привет от Left"
class Right(Base):
def greet(self):
return "привет от Right"
class Child(Left, Right):
pass
print(Child().greet())
print([cls.__name__ for cls in Child.__mro__])
привет от Left ['Child', 'Left', 'Right', 'Base', 'object']
| Приём | Что даёт |
|---|---|
| Миксин | Класс-добавка без своего __init__ и полей: JSONMixin даёт to_json(), ReprMixin — аккуратный __repr__ |
| Порядок родителей | Миксины первыми, базовый класс последним: class Book(JSONMixin, ReprMixin) — методы миксинов главнее |
| MRO (__mro__) | Линейный порядок поиска методов; в алмазе Child ищет по цепочке Child → Left → Right → Base |
| Кооперативный super() | Каждый класс в цепочке вызывает super().__init__(**kwargs), и все инициализаторы срабатывают по MRO |
Кооперативный super()
class Logger:
def __init__(self, **kwargs):
print("Logger настраивает журнал")
super().__init__(**kwargs)
class Saver:
def __init__(self, **kwargs):
print("Saver готовит хранилище")
super().__init__(**kwargs)
class Service(Logger, Saver):
def __init__(self):
print("Service запускается")
super().__init__()
Service()
Service запускается Logger настраивает журнал Saver готовит хранилище
Сначала предскажи ответ в голове — это главный навык программиста.
class Base:
def who(self):
return "base"
class Left(Base):
def who(self):
return "left"
class Right(Base):
def who(self):
return "right"
class Child(Left, Right):
pass
print(Child().who())
class LoudMixin:
def loud(self):
return self.sound().upper() + "!"
class Frog:
def sound(self):
return "ква"
class LoudFrog(LoudMixin, Frog):
pass
print(LoudFrog().loud())
class A:
def __init__(self, **kw):
super().__init__(**kw)
self.tag = "A"
class B(A):
def __init__(self, **kw):
super().__init__(**kw)
self.tag += "B"
class C(A):
def __init__(self, **kw):
super().__init__(**kw)
self.tag += "C"
class D(B, C):
def __init__(self):
super().__init__()
print(D().tag)
1. Как Python ищет метод у класса с несколькими родителями?
2. Что такое миксин?
3. Куда приводит super() из класса Left при иерархии Child(Left, Right)?
4. Что даёт кооперативный вызов super().__init__(**kwargs) в каждом классе цепочки?
5. Почему миксины ставят в начало списка родителей?
6. Когда множественное наследование — плохой выбор?
Создайте миксин UpperMixin с методом loud(), возвращающим self.sound().upper() + "!". Опишите класс Duck со методом sound(), возвращающим «кря», и класс LoudDuck(UpperMixin, Duck). Выведите результат LoudDuck().loud().
Что такое множественное наследование в Python?
Возможность класса перечислить несколько родителей: class Book(JSONMixin, ReprMixin). Python ищет методы по линейному порядку MRO — его можно увидеть в атрибуте __mro__. На практике множественное наследование применяют в форме миксинов: небольших классов-добавок поведения.
Урок 18: миксины и MROЧто такое миксин и зачем он нужен?
Миксин — класс без своего состояния (без __init__ и полей), который добавляет методы любому классу-носителю: to_json, аккуратный __repr__, сравнение. Миксины перечисляют первыми в списке родителей, чтобы их методы находились раньше методов базового класса. Так поведение собирается из независимых добавок, без дублирования кода.
Как Python решает алмазную проблему?
Линеаризацией: строится единый порядок MRO, в котором каждый класс встречается один раз, и метод ищется строго по нему — в Child(Left, Right) победит версия из Left. Для инициализации используется кооперативный super(): каждый __init__ передаёт ход следующему классу MRO, поэтому родительский конструктор не вызывается дважды.
Чем super() отличается от вызова родителя по имени?
super() — это «следующий класс в MRO», а не конкретный родитель: в иерархии Child(Left, Right) вызов super() из Left ведёт в Right. Прямой вызов Base.__init__(self) такое не умеет: он жёстко привязан к одному классу и ломает кооперативную цепочку, из-за чего часть инициализаторов не сработает.
Понравился урок? Сошлитесь на него
Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.
Что читать дальше
ООП · Урок 17
Декораторы: функции над функциями
Декоратор — функция, которая принимает функцию и возвращает новую, добавляя поведение без правки исходного кода. Соберём декоратор с нуля: от замыкания и синтаксиса @ до functools.wraps, декораторов для методов и декораторов с параметрами.
ООП · Урок 19
Паттерны на классах: синглтон и фабрика
Паттерны — проверенные ответы на повторяющиеся задачи проектирования. Соберём три на чистых классах: синглтон через __new__, фабричный метод через @classmethod и стратегию скидок через полиморфизм — а заодно договоримся, когда паттерн только мешает.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
SQLAlchemy · Урок 19
N+1: главная ловушка ORM и selectinload
Удобный атрибут user.tasks в цикле — это по запросу на каждого автора: знаменитое N+1, главная ловушка ORM. Лечится одной строкой — selectinload.
n+1 проблема ormsqlalchemy lazy loading проблема
Pandas · Урок 5
Группировка groupby в Pandas: агрегация как в Excel, только мощнее
groupby, agg и pivot_table — считаем выручку по городам и товарам, строим сводные таблицы и не теряем строки с NaN.
groupby несколько столбцов
ООП · Урок 7
Наследование: расширяем классы
Наследование — способ расширять классы без переписывания: потомок получает всё от родителя, переопределяет и дополняет через super(). Родство проверяет isinstance.
наследование pythonpython наследование классов