Что такое миксин и почему они полезны? - PullRequest
829 голосов
/ 10 февраля 2009

В « Programming Python » Марк Лутц упоминает «миксин». Я из C / C ++ / C # фона, и я не слышал этот термин раньше. Что такое миксин?

Чтение между строк этого примера (с которым я связался, потому что оно довольно длинное), я предполагаю, что это случай использования множественного наследования для расширения класса в отличие от «правильного» подклассы. Это правильно?

Зачем мне это делать, а не помещать новую функциональность в подкласс? В этом отношении, почему подход смешанного / множественного наследования лучше, чем использование композиции?

Что отличает миксин от множественного наследования? Это просто вопрос семантики?

Ответы [ 15 ]

609 голосов
/ 14 февраля 2009

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

  1. Вы хотите предоставить множество дополнительных функций для класса.
  2. Вы хотите использовать одну особенность во множестве разных классов.

Для примера номер один рассмотрим систему запросов и ответов werkzeug . Я могу сделать простой старый объект запроса, сказав:

from werkzeug import BaseRequest

class Request(BaseRequest):
    pass

Если я хочу добавить поддержку заголовка accept, я бы сделал это

from werkzeug import BaseRequest, AcceptMixin

class Request(AcceptMixin, BaseRequest):
    pass

Если бы я хотел создать объект запроса, который поддерживает заголовки принятия, etags, аутентификацию и поддержку пользовательских агентов, я мог бы сделать это:

from werkzeug import BaseRequest, AcceptMixin, ETagRequestMixin, UserAgentMixin, AuthenticationMixin

class Request(AcceptMixin, ETagRequestMixin, UserAgentMixin, AuthenticationMixin, BaseRequest):
    pass

Разница невелика, но в вышеприведенных примерах классы mixin не были созданы для самостоятельной работы. В более традиционном множественном наследовании AuthenticationMixin (например), вероятно, будет чем-то более похожим на Authenticator. То есть класс, вероятно, будет спроектирован так, чтобы он стоял сам по себе.

205 голосов
/ 10 февраля 2009

Во-первых, вы должны заметить, что миксины существуют только в языках множественного наследования. Вы не можете сделать миксин в Java или C #.

По сути, миксин - это автономный базовый тип, который обеспечивает ограниченную функциональность и полиморфный резонанс для дочернего класса. Если вы думаете о C #, подумайте об интерфейсе, который вам не нужно реализовывать, потому что он уже реализован; Вы просто наследуете его и извлекаете выгоду из его функциональности.

Миксины обычно узкие по объему и не предназначены для расширения.

[править - почему:]

Полагаю, мне следует спросить почему, так как вы спросили. Большим преимуществом является то, что вам не нужно делать это снова и снова. В C # самое большое место, где миксин мог бы извлечь выгоду, могло бы быть из Схема утилизации . Всякий раз, когда вы реализуете IDisposable, вы почти всегда хотите следовать одному и тому же шаблону, но в итоге вы пишете и переписываете один и тот же базовый код с небольшими изменениями. Если бы существовал расширяемый миксин Disposal, вы могли бы сэкономить много лишнего набора текста.

[править 2 - чтобы ответить на другие ваши вопросы]

Что отличает миксин от множественного наследования? Это просто вопрос семантики?

Да. Разница между миксином и стандартным множественным наследованием является лишь вопросом семантики; класс с множественным наследованием может использовать миксин как часть этого множественного наследования.

Смысл mixin состоит в том, чтобы создать тип, который можно «смешать» с любым другим типом посредством наследования, не затрагивая тип наследования, и в то же время предлагать некоторые полезные функции для этого типа.

Опять же, подумайте об интерфейсе, который уже реализован.

Лично я не использую миксины, так как я разрабатываю в основном на языке, который их не поддерживает, поэтому мне очень трудно придумать достойный пример, который просто предоставит это "ахах!" момент для вас. Но я попробую еще раз. Я собираюсь использовать надуманный пример - большинство языков уже так или иначе предоставляют эту функцию - но, надеюсь, это объяснит, как миксины должны создаваться и использоваться. Здесь идет:

Предположим, у вас есть тип, который вы хотите иметь возможность сериализации в и из XML. Вы хотите, чтобы тип предоставил метод «ToXML», который возвращает строку, содержащую фрагмент XML со значениями данных типа, и «FromXML», который позволяет типу восстанавливать свои значения данных из фрагмента XML в строку. Опять же, это надуманный пример, поэтому, возможно, вы используете файловый поток или класс XML Writer из библиотеки времени выполнения вашего языка ... что угодно. Дело в том, что вы хотите сериализовать свой объект в XML и получить новый объект обратно из XML.

Другим важным моментом в этом примере является то, что вы хотите сделать это в общем виде. Вам не нужно реализовывать методы «ToXML» и «FromXML» для каждого типа, который вы хотите сериализовать, вам нужны какие-то общие средства, гарантирующие, что ваш тип сделает это, и он просто работает. Вы хотите повторного использования кода.

Если ваш язык поддерживает это, вы можете создать миксин XmlSerializable, который сделает вашу работу за вас. Этот тип будет реализовывать методы ToXML и FromXML. Используя некоторый механизм, который не важен для примера, он мог бы собрать все необходимые данные из любого типа, с которым он смешан, для создания фрагмента XML, возвращаемого ToXML, и он был бы в равной степени способен восстановить эти данные, когда FromXML называется.

И .. вот и все. Чтобы использовать его, вы должны иметь любой тип, который нужно сериализовать, чтобы XML наследовал от XmlSerializable. Когда бы вам ни понадобилось сериализовать или десериализовать этот тип, вы просто вызывали бы ToXML или FromXML. Фактически, поскольку XmlSerializable является полноценным типом и полиморфным, вы могли бы создать сериализатор документов, который ничего не знает о вашем исходном типе, принимая только, скажем, массив типов XmlSerializable.

Теперь представьте, что вы можете использовать этот сценарий для других целей, таких как создание миксина, который гарантирует, что каждый класс, который смешивает его, регистрирует каждый вызов метода, или миксина, который обеспечивает транзакционность для типа, который его смешивает. Список можно продолжать и на.

Если вы просто думаете о миксине как о небольшом базовом типе, предназначенном для добавления небольшого количества функциональности к типу, без какого-либо влияния на этот тип, то вы великолепны.

Надеюсь. :)

143 голосов

Цель этого ответа - объяснить миксины с примерами :

  • автономный : короткий, без необходимости знать какие-либо библиотеки для понимания примера.

  • на Python , но не на других языках.

    Понятно, что были примеры из других языков, таких как Ruby, поскольку этот термин гораздо чаще встречается в этих языках, но это Python thread.

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

Необходимо ли множественное наследование или нет для характеристики миксина?

Определения

Мне еще предстоит увидеть цитату из "авторитетного" источника, в которой четко сказано, что такое миксин в Python.

Я видел 2 возможных определения миксина (если их следует считать отличными от других подобных концепций, таких как абстрактные базовые классы), и люди не совсем согласны с тем, какое из них правильное.

Консенсус может отличаться для разных языков.

Определение 1: нет множественного наследования

Миксин - это такой класс, что некоторый метод класса использует метод, который не определен в классе.

Следовательно, этот класс не предназначен для создания экземпляра, а скорее служит базовым классом. В противном случае экземпляр будет иметь методы, которые не могут быть вызваны без вызова исключения.

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

Классическим примером является реализация всех операторов сравнения только с <= и ==:

class ComparableMixin(object):
    """This class has methods which use `<=` and `==`,
    but this class does NOT implement those methods."""
    def __ne__(self, other):
        return not (self == other)
    def __lt__(self, other):
        return self <= other and (self != other)
    def __gt__(self, other):
        return not self <= other
    def __ge__(self, other):
        return self == other or self > other

class Integer(ComparableMixin):
    def __init__(self, i):
        self.i = i
    def __le__(self, other):
        return self.i <= other.i
    def __eq__(self, other):
        return self.i == other.i

assert Integer(0) <  Integer(1)
assert Integer(0) != Integer(1)
assert Integer(1) >  Integer(0)
assert Integer(1) >= Integer(1)

# It is possible to instantiate a mixin:
o = ComparableMixin()
# but one of its methods raise an exception:
#o != o 

Этот конкретный пример мог быть реализован через декоратор functools.total_ordering(), но игра здесь заключалась в том, чтобы заново изобрести колесо:

import functools

@functools.total_ordering
class Integer(object):
    def __init__(self, i):
        self.i = i
    def __le__(self, other):
        return self.i <= other.i
    def __eq__(self, other):
        return self.i == other.i

assert Integer(0) < Integer(1)
assert Integer(0) != Integer(1)
assert Integer(1) > Integer(0)
assert Integer(1) >= Integer(1)

Определение 2: множественное наследование

Миксин - это шаблон проектирования, в котором некоторый метод базового класса использует метод, который он не определяет, и этот метод предназначен для реализации другим базовым классом , а не производным, как в Определение 1.

Термин mixin class относится к базовым классам, которые предназначены для использования в этом шаблоне проектирования (TODO, те, которые используют метод, или те, которые его реализуют?)

Нелегко решить, является ли данный класс миксином или нет: метод может быть просто реализован в производном классе, и в этом случае мы вернемся к определению 1. Вы должны учитывать намерения автора.

Этот шаблон интересен тем, что можно комбинировать функции с различными вариантами выбора базовых классов:

class HasMethod1(object):
    def method(self):
        return 1

class HasMethod2(object):
    def method(self):
        return 2

class UsesMethod10(object):
    def usesMethod(self):
        return self.method() + 10

class UsesMethod20(object):
    def usesMethod(self):
        return self.method() + 20

class C1_10(HasMethod1, UsesMethod10): pass
class C1_20(HasMethod1, UsesMethod20): pass
class C2_10(HasMethod2, UsesMethod10): pass
class C2_20(HasMethod2, UsesMethod20): pass

assert C1_10().usesMethod() == 11
assert C1_20().usesMethod() == 21
assert C2_10().usesMethod() == 12
assert C2_20().usesMethod() == 22

# Nothing prevents implementing the method
# on the base class like in Definition 1:

class C3_10(UsesMethod10):
    def method(self):
        return 3

assert C3_10().usesMethod() == 13

Официальные появления Python

В официальном документе для collection.abc в документации явно используется термин Методы смешивания .

В нем говорится, что если класс:

  • орудия __next__
  • наследуется от одного класса Iterator

тогда класс получает __iter__ метод mixin бесплатно.

Поэтому, по крайней мере, в этом пункте документации, mixin не требует множественного наследования и соответствует определению 1.

Документация, конечно, может быть противоречивой в разные моменты, и другие важные библиотеки Python могут использовать другое определение в своей документации.

На этой странице также используется термин Set mixin, который ясно указывает на то, что классы, подобные Set и Iterator, можно назвать классами Mixin.

На других языках

  • Ruby: Очевидно, что не требуется множественное наследование для mixin, как упоминалось в основных справочниках, таких как Programming Ruby и Язык программирования Ruby

  • C ++: Метод, который не реализован, является чисто виртуальным методом.

    Определение 1 совпадает с определением абстрактного класса (класса, который имеет чисто виртуальный метод). Этот класс не может быть создан.

    Определение 2 возможно с виртуальным наследованием: Множественное наследование от двух производных классов

32 голосов
/ 05 июля 2013

Я считаю их дисциплинированным способом использования множественного наследования, потому что в конечном итоге миксин - это просто еще один класс Python, который (может) следовать соглашениям о классах, называемых миксинами.

Мое понимание соглашений, которые регулируют то, что вы бы назвали миксином, таково, что миксин:

  • добавляет методы, но не переменные экземпляра (константы класса в порядке)
  • наследуется только от object (в Python)

Таким образом, он ограничивает потенциальную сложность множественного наследования и позволяет достаточно легко отслеживать поток вашей программы, ограничивая область поиска (по сравнению с полным множественным наследованием). Они похожи на ruby-модули .

Если я хочу добавить переменные экземпляра (с большей гибкостью, чем допускается одиночным наследованием), я склоняюсь к компоновке.

Сказав это, я видел классы с именем XYZMixin, которые имеют переменные экземпляра.

26 голосов
/ 13 июня 2012

Mixins - это концепция в программировании, в которой класс предоставляет функциональные возможности, но он не предназначен для использования в экземплярах. Основная цель Mixins состоит в том, чтобы обеспечить функциональные возможности, которые являются автономными, и было бы лучше, если бы сами миксины не наследовали другие миксины и также избегали состояния. В таких языках, как Ruby, есть прямая языковая поддержка, но для Python нет. Однако вы можете использовать наследование нескольких классов для выполнения функций, предоставляемых в Python.

Я смотрел это видео http://www.youtube.com/watch?v=v_uKI2NOLEM, чтобы понять основы миксинов. Для новичка очень полезно понять основы миксинов и то, как они работают, и проблемы, с которыми вы можете столкнуться при их реализации.

Википедия по-прежнему лучшая: http://en.wikipedia.org/wiki/Mixin

21 голосов
/ 25 марта 2016

Что отличает миксин от множественного наследования? Это просто вопрос семантики?

Миксин - это ограниченная форма множественного наследования. В некоторых языках механизм добавления миксина в класс немного отличается (с точки зрения синтаксиса) от механизма наследования.

В частности, в контексте Python, mixin является родительским классом, который обеспечивает функциональность для подклассов, но не предназначен для самореализации.

Что может заставить вас сказать, что «это просто множественное наследование, а не миксин», так это то, что класс, который может быть сбит с толку за миксин, действительно может быть создан и использован - так что это действительно семантическая и очень реальная, разница.

Пример множественного наследования

Этот пример, из документации , является OrderedCounter:

class OrderedCounter(Counter, OrderedDict):
     'Counter that remembers the order elements are first encountered'

     def __repr__(self):
         return '%s(%r)' % (self.__class__.__name__, OrderedDict(self))

     def __reduce__(self):
         return self.__class__, (OrderedDict(self),)

Он подклассов Counter и OrderedDict из collections модуля.

И Counter, и OrderedDict предназначены для создания и использования самостоятельно. Однако, подклассифицируя их обоих, мы можем получить счетчик, который упорядочен и повторно использует код в каждом объекте.

Это мощный способ повторного использования кода, но он также может быть проблематичным. Если окажется, что в одном из объектов есть ошибка, ее исправление может привести к ошибке в подклассе.

Пример миксина

Миксины обычно рекламируются как способ повторного использования кода без потенциальных проблем связывания, которые могут иметь кооперативное множественное наследование, например, OrderedCounter. Когда вы используете миксины, вы используете функциональность, которая не так тесно связана с данными.

В отличие от приведенного выше примера, миксин не предназначен для самостоятельного использования. Предоставляет новую или другую функциональность.

Например, в стандартной библиотеке есть пара mixins в библиотеке socketserver .

Можно создавать версии Forking и Threading для каждого типа сервера. используя эти смешанные классы. Например, ThreadingUDPServer является создан следующим образом:

class ThreadingUDPServer(ThreadingMixIn, UDPServer):
    pass

Первым является смешанный класс, поскольку он переопределяет метод, определенный в UDPServer. Установка различных атрибутов также меняет поведение базовый серверный механизм.

В этом случае методы mixin переопределяют методы в определении объекта UDPServer для обеспечения параллелизма.

Переопределенный метод выглядит как process_request, и он также предоставляет другой метод, process_request_thread. Вот он из исходного кода :

class ThreadingMixIn:
        """Mix-in class to handle each request in a new thread."""

        # Decides how threads will act upon termination of the
        # main process
        daemon_threads = False

        def process_request_thread(self, request, client_address):
            """Same as in BaseServer but as a thread.
            In addition, exception handling is done here.
            """
            try:
                self.finish_request(request, client_address)
            except Exception:
                self.handle_error(request, client_address)
            finally:
                self.shutdown_request(request)

        def process_request(self, request, client_address):
            """Start a new thread to process the request."""
            t = threading.Thread(target = self.process_request_thread,
                                 args = (request, client_address))
            t.daemon = self.daemon_threads
            t.start()

Придуманный пример

Это миксин, который в основном предназначен для демонстрации - большинство объектов будут развиваться за пределами полезности этого отчета:

class SimpleInitReprMixin(object):
    """mixin, don't instantiate - useful for classes instantiable
    by keyword arguments to their __init__ method.
    """
    __slots__ = () # allow subclasses to use __slots__ to prevent __dict__
    def __repr__(self):
        kwarg_strings = []
        d = getattr(self, '__dict__', None)
        if d is not None:
            for k, v in d.items():
                kwarg_strings.append('{k}={v}'.format(k=k, v=repr(v)))
        slots = getattr(self, '__slots__', None)
        if slots is not None:
            for k in slots:
                v = getattr(self, k, None)
                kwarg_strings.append('{k}={v}'.format(k=k, v=repr(v)))
        return '{name}({kwargs})'.format(
          name=type(self).__name__,
          kwargs=', '.join(kwarg_strings)
          )

и использование будет:

class Foo(SimpleInitReprMixin): # add other mixins and/or extend another class here
    __slots__ = 'foo',
    def __init__(self, foo=None):
        self.foo = foo
        super(Foo, self).__init__()

И использование:

>>> f1 = Foo('bar')
>>> f2 = Foo()
>>> f1
Foo(foo='bar')
>>> f2
Foo(foo=None)
10 голосов
/ 26 декабря 2014

Я думаю, что здесь было несколько хороших объяснений, но я хотел представить другую точку зрения.

В Scala вы можете создавать миксины, как было описано здесь, но очень интересно то, что миксины фактически «слиты» вместе, чтобы создать новый тип класса для наследования. По сути, вы не наследуете от нескольких классов / миксинов, а генерируете новый тип классов со всеми свойствами миксина для наследования. Это имеет смысл, поскольку Scala основана на JVM, где множественное наследование в настоящее время не поддерживается (начиная с Java 8). Этот тип класса mixin, кстати, является специальным типом, называемым Trait в Scala.

На это намекает способ определения класса: класс NewClass расширяет FirstMixin с помощью SecondMixin с помощью ThirdMixin ...

Я не уверен, что интерпретатор CPython делает то же самое (миксин-класс-композиция), но я не удивлюсь. Кроме того, исходя из фона C ++, я бы не назвал ABC или «интерфейс» эквивалентом mixin - это похожая концепция, но расходящаяся в использовании и реализации.

10 голосов
/ 10 февраля 2009

Я бы посоветовал не добавлять в новый код Python дополнительные модули, если вы можете найти какой-либо другой способ его обойти (например, метод «составление вместо наследования» или просто применение методов «обезьяньих патчей» в ваших собственных классах), который не ' гораздо больше усилий.

В классах старого стиля вы можете использовать дополнения как способ получения нескольких методов из другого класса. Но в мире нового стиля все, даже микширование, наследуется от object. Это означает, что любое использование множественного наследования естественным образом вызывает проблемы MRO .

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

8 голосов
/ 11 февраля 2009

Возможно, пара примеров поможет.

Если вы создаете класс и хотите, чтобы он действовал как словарь, вы можете определить все необходимые методы __ __. Но это немного больно. В качестве альтернативы вы можете просто определить несколько и наследовать (в дополнение к любому другому наследованию) от UserDict.DictMixin (перемещено в collections.DictMixin в py3k). Это приведет к автоматическому определению всех остальных словаря API.

Второй пример: инструментарий GUI wxPython позволяет создавать элементы управления списком с несколькими столбцами (как, например, отображение файла в проводнике Windows). По умолчанию эти списки являются довольно простыми. Вы можете добавить дополнительные функции, такие как возможность сортировки списка по определенному столбцу, щелкнув заголовок столбца, унаследовав от ListCtrl и добавив соответствующие миксины.

7 голосов
/ 18 февраля 2009

Это не пример Python, но в языке программирования D термин mixin используется для обозначения конструкции, используемой почти таким же образом; добавив кучу вещей в класс.

В D (который, кстати, не делает MI) это делается путем вставки шаблона (подумайте о синтаксически безопасных и безопасных макросах, и вы будете близки) в область действия. Это позволяет использовать одну строку кода в классе, структуре, функции, модуле или любом другом объекте для расширения до любого количества объявлений.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...