Mixin классы - это концепция в программировании, в которой класс предоставляет функциональные возможности, но не предназначен для самостоятельного использования. Основная цель миксинов - предоставить какие-то дополнительные методы.
Другими словами классы миксины или как еще их называют примеси - это ограниченная форма множественного наследования. В частности, в контексте языка Python, миксин - это родительский класс, который предоставляет функциональные возможности подклассам, но не предназначен для создания экземпляров самого себя. И было бы лучше, если бы сами миксины не имели наследования от других миксинов, а также избегали какого либо состояния.
В Python нет какого либо специального синтаксиса для поддержки миксинов, по этому классы миксинов, легко можно перепутать с обычными классами, но при этом у них действительно очень большая как семантическая, так и реальная разница с обычными классами.
Так как для реализации поведения миксинов используется простое множественное наследование, то это требует от программиста большой дисциплины, поскольку нарушает одно из основных допущений для миксинов: их ортогональность к дереву наследования, т. е. классы, не зависят друг от друга. В Python миксины живут в обычном дереве наследования, предоставляя дополнительную функциональность и избегают создания иерархий, которые слишком сложны для понимания программистом.
from collections import Counter, OrderedDict
class OrderedCounter(Counter, OrderedDict):
"""Счетчик, который запоминает порядок,
в котором элементы встречаются впервые"""
def __repr__(self):
return f'{self.__class__.__name__}({OrderedDict(self)!r})'
def __reduce__(self):
return self.__class__, (OrderedDict(self),)
>>> od = OrderedCounter()
>>> repr(od)
# 'OrderedCounter(OrderedDict())'
Он создает подкласс Counter
и OrderedDict
, которые импортируются из модуля collections
.
И Counter
, и OrderedDict
предназначены для самостоятельного использования в качестве экземпляров. Создав подкласс из обоих классов, получаем счетчик, который будет упорядочен и повторно использует код в каждом объекте. Это мощный способ повторного использования кода, но он также может быть проблематичным. Так как, если выяснится, что в одном из объектов есть ошибка, то ее исправление может создать ошибку в подклассе.
Миксины обычно продвигаются как способ повторного использования кода без потенциальных проблем связанности, которые могут возникнуть при кооперативном множественном наследовании, таком как в OrderedCounter()
. Когда используются миксины, то по сути используется функциональность, которая не так тесно связана с данными.
В отличие от приведенного выше примера, класс миксина не предназначен для использования отдельно. Он предоставляет новые методы или переопределяет имеющиеся методы.
Например, в стандартной библиотеке Python, в модуле socketserver
есть несколько миксинов. Выдержка из документации:
С помощью этих классов миксинов могут быть созданы поточные версии каждого типа сервера. Например, ThreadingUDPServer
создается следующим образом:
class ThreadingUDPServer(ThreadingMixIn, UDPServer):
pass
Первым, идет класс миксина ThreadingMixIn
, так как он переопределяет метод process_request()
и server_close()
, определенный в классе UDPServer()
, к тому же, добавляет новую функциональность, чтобы обеспечить параллелизм, а именно - новый метод process_request_thread()
.
Классы миксинов в Python - это действительно отличная концепция, которая позволяет создавать классы в композиционном стиле.
class Entity:
def __init__(self, pos_x, pos_y):
self.pos_x = pos_x
self.pos_y = pos_y
class SquareMixin:
def add_size(self, size_x):
self.size_x = size_x
self.size_y = size_x
def perimeter(self):
return self.size_x * 4
def square(self):
return self.size_x * self.size_x
class SquareEntity(SquareMixin, Entity):
pass
>>> square = SquareEntity(5, 4)
>>> square.add_size(500)
>>> square.size_x
# 500
>>> square.size_y
# 500
>>> square.square()
# 250000
>>> square.perimeter()
# 2000
Здесь итоговый класс SquareEntity()
получает от класса миксина SquareMixin()
методы добавления размера квадрата, а так же вычисления его периметра и площади. Данное поведение упрощает дерево наследования SquareEntity()
, что позволяет использовать класс Entity()
в качестве родителя для других фигур без необходимости наследовать методы, которые не нужны (например для круга).
Иногда молодые программисты не до конца понимают принцип MRO в Python и по этому в некоторых случаях не правильно используют классы миксинов.
Пример:
class BaseClass:
def test(self):
print('BaseClass')
class Mixin:
def test(self):
print('Mixin')
class MyClass(BaseClass, Mixin):
pass
>>> obj = MyClass()
# класс миксина не работает
>>> obj.test()
# BaseClass
В Python иерархия классов определяется справа налево, поэтому в этом случае класс Mixin()
является базовым классом и расширяется BaseClass()
. Обычно это нормально, потому что во многих случаях классы миксинов не переопределяют методы друг друга или базового класса. Но если в миксинах идет переопределение метода или свойства, то это может привести к неожиданным результатам, т. к. приоритет разрешения методов - слева направо.
class MyClass(Mixin, BaseClass):
pass
>>> obj = MyClass()
# теперь все нормально
>>> obj.test()
Mixin