Pregunta ¿Qué es un Mixin, y por qué son útiles?


En "Programando Python"Mark Lutz menciona" mixins ". Tengo antecedentes de C / C ++ / C # y no escuché el término antes. ¿Qué es un mixin?

Leyendo entre las líneas de este ejemplo (a la que me he vinculado porque es bastante larga), supongo que se trata de usar herencia múltiple para extender una clase en lugar de una subclase 'adecuada'. ¿Es esto correcto?

¿Por qué querría hacer eso en lugar de poner la nueva funcionalidad en una subclase? Para el caso, ¿por qué sería mejor un enfoque de herencia mixin / múltiple que usar composición?

¿Qué separa a un mixin de una herencia múltiple? ¿Es solo una cuestión de semántica?


727
2018-02-10 18:50


origen


Respuestas:


Una mixin es un tipo especial de herencia múltiple. Hay dos situaciones principales donde se usan mixinas:

  1. Desea proporcionar muchas características opcionales para una clase.
  2. Desea utilizar una característica particular en muchas clases diferentes.

Para un ejemplo de número uno, considere Sistema de solicitud y respuesta de werkzeug. Puedo hacer un viejo objeto simple de solicitud diciendo:

from werkzeug import BaseRequest

class Request(BaseRequest):
    pass

Si quiero agregar aceptar el soporte de encabezado, lo haría

from werkzeug import BaseRequest, AcceptMixin

class Request(AcceptMixin, BaseRequest):
    pass

Si quisiera crear un objeto de solicitud que admita la aceptación de encabezados, etags, autenticación y agente de usuario, podría hacer esto:

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

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

La diferencia es sutil, pero en los ejemplos anteriores, las clases de mixin no estaban hechas para sostenerse por sí mismas. En la herencia múltiple más tradicional, la AuthenticationMixin (por ejemplo) probablemente sería algo más parecido Authenticator. Es decir, la clase probablemente estaría diseñada para mantenerse por sí misma.


536
2018-02-13 21:15



En primer lugar, debe tener en cuenta que los mixins solo existen en lenguajes de herencia múltiple. No puedes hacer mixin en Java o C #.

Básicamente, un mixin es un tipo base independiente que proporciona funcionalidad limitada y resonancia polimórfica para una clase infantil. Si está pensando en C #, piense en una interfaz que no tiene que implementar realmente porque ya está implementada; simplemente hereda de él y se beneficia de su funcionalidad.

Mixins son típicamente estrechas en su alcance y no están destinadas a ser extendidas.

[editar - por qué:]

Supongo que debería abordar el por qué, ya que me preguntaste. El gran beneficio es que no tiene que hacerlo usted mismo una y otra vez. En C #, el lugar más grande donde podría beneficiarse un mixin podría ser el Patrón de eliminación. Cada vez que implemente IDisposable, casi siempre desea seguir el mismo patrón, pero termina escribiendo y reescribiendo el mismo código básico con variaciones menores. Si hubiera una mezcla de eliminación extensible, podría ahorrarse una gran cantidad de tipeo extra.

[Editar 2 - para responder tus otras preguntas]

¿Qué separa a un mixin de una herencia múltiple? ¿Es solo una cuestión de semántica?

Sí. La diferencia entre una mezcla y una herencia múltiple estándar es solo una cuestión de semántica; una clase que tiene herencia múltiple puede utilizar un mixin como parte de esa herencia múltiple.

El objetivo de un mixin es crear un tipo que se pueda "mezclar" con cualquier otro tipo a través de la herencia sin afectar el tipo de herencia mientras que todavía ofrece alguna funcionalidad beneficiosa para ese tipo.

Nuevamente, piense en una interfaz que ya está implementada.

Yo personalmente no uso mixins ya que me desarrollo principalmente en un lenguaje que no los admite, así que estoy teniendo un momento muy difícil para encontrar un ejemplo decente que me proporcione ese "¡Ajá!" momento para ti Pero lo intentaré de nuevo. Voy a usar un ejemplo inventado, la mayoría de los lenguajes ya proporcionan la función de una manera u otra, pero eso explicará, afortunadamente, cómo se deben crear y utilizar mixins. Aquí va:

Supongamos que tiene un tipo al que desea serializar y desde XML. Desea que el tipo proporcione un método "ToXML" que devuelve una cadena que contiene un fragmento XML con los valores de datos del tipo, y un "FromXML" que permite que el tipo reconstruya sus valores de datos a partir de un fragmento XML en una cadena. De nuevo, este es un ejemplo artificial, por lo que quizás use una secuencia de archivos o una clase XML Writer de la biblioteca de tiempo de ejecución de su lenguaje ... lo que sea. El punto es que desea serializar su objeto a XML y obtener un nuevo objeto de XML.

El otro punto importante en este ejemplo es que desea hacer esto de forma genérica. No desea tener que implementar un método "ToXML" y "FromXML" para cada tipo que desee serializar, desea algunos medios genéricos para garantizar que su tipo lo haga y simplemente funciona. Quieres la reutilización del código.

Si su idioma lo admite, puede crear la mezcla XmlSerializable para que haga su trabajo por usted. Este tipo implementaría los métodos ToXML y FromXML. Utilizaría algún mecanismo que no es importante para el ejemplo, sería capaz de reunir todos los datos necesarios de cualquier tipo con el que esté mezclado para construir el fragmento XML devuelto por ToXML y sería igualmente capaz de restaurar esos datos cuando FromXML es llamado.

Y eso es. Para usarlo, tendría cualquier tipo que necesite ser serializado para heredar XML de XmlSerializable. Siempre que necesite serializar o deserializar ese tipo, simplemente debe llamar a ToXML o FromXML. De hecho, dado que XmlSerializable es un tipo completamente desarrollado y polimórfico, podría concebirse construir un serializador de documentos que no sepa nada sobre su tipo original, aceptando solo, digamos, una matriz de tipos XmlSerializable.

Ahora imagina usar este escenario para otras cosas, como crear una mezcla que asegure que cada clase que lo mezcla registre cada llamada a un método, o una mezcla que proporcione transaccionalidad al tipo que la mezcla. La lista puede seguir y seguir.

Si solo piensas en un Mixin como un tipo de base pequeño diseñado para agregar una pequeña cantidad de funcionalidad a un tipo sin afectar a ese tipo, entonces estás dorado.

Ojalá. :)


195
2018-02-10 19:00



Esta respuesta tiene como objetivo explicar los mixins con ejemplos que son:

  • autónomo: corto, sin necesidad de conocer ninguna biblioteca para comprender el ejemplo.

  • en Python, no en otros idiomas

    Es comprensible que haya ejemplos de otros idiomas, como Ruby, ya que el término es mucho más común en esos idiomas, pero este es un Pitón hilo.

También considerará la cuestión controvertida:

¿Es necesaria o no la herencia múltiple para caracterizar un mixin?

Definiciones

Aún no he visto una cita de una fuente "autorizada" que diga claramente qué es una mezcla en Python.

He visto 2 posibles definiciones de mixin (si deben considerarse diferentes de otros conceptos similares, como las clases base abstractas), y las personas no están totalmente de acuerdo sobre cuál es la correcta.

El consenso puede variar entre diferentes idiomas.

Definición 1: sin herencia múltiple

Una mixin es una clase tal que algún método de la clase usa un método que no está definido en la clase.

Por lo tanto, la clase no debe crearse una instancia, sino que debe servir como una clase base. De lo contrario, la instancia tendría métodos que no se pueden llamar sin generar una excepción.

Una restricción que algunas fuentes agregan es que la clase puede no contener datos, solo métodos, pero no veo por qué es necesario. En la práctica, sin embargo, muchos mixins útiles no tienen ningún dato, y las clases base sin datos son más simples de usar.

Un ejemplo clásico es la implementación de todos los operadores de comparación desde solo <= y ==:

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 

Este ejemplo particular podría haberse logrado a través del functools.total_ordering() decorador, pero el juego aquí era reinventar la rueda:

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)

Definición 2: herencia múltiple

Un mixin es un patrón de diseño en el que algún método de una clase base utiliza un método que no define, y ese método debe ser implementado por otra clase base, no por el derivado como en la definición 1.

El termino clase mixin se refiere a las clases base que están destinadas a ser utilizadas en ese patrón de diseño (TODO aquellas que usan el método, o aquellas que lo implementan?)

No es fácil decidir si una clase dada es mixin o no: el método podría simplemente implementarse en la clase derivada, en cuyo caso volveremos a la Definición 1. Debes considerar las intenciones del autor.

Este patrón es interesante porque es posible recombinar funcionalidades con diferentes opciones de clases base:

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

Ocurrencias autorizadas de Python

En el documentatiton oficial para colecciones.abc la documentación usa explícitamente el término Métodos Mixin.

Indica que si una clase:

  • implementos __next__
  • hereda de una sola clase Iterator

entonces la clase obtiene una __iter__  método mixin gratis.

Por lo tanto, al menos en este punto de la documentación, Mixin no requiere herencia múltiple, y es coherente con la definición 1.

La documentación podría, por supuesto, ser contradictoria en diferentes puntos, y otras bibliotecas importantes de Python podrían estar usando la otra definición en su documentación.

Esta página también usa el término Set mixin, lo que sugiere claramente que clases como Set y Iteratorse pueden llamar clases Mixin.

En otros idiomas

  • Ruby: Claramente no requiere herencia múltiple para mixin, como se menciona en los principales libros de referencia, como Programación Ruby y el lenguaje de programación Ruby

  • C ++: un método que no está implementado es un método virtual puro.

    La definición 1 coincide con la definición de una clase abstracta (una clase que tiene un método virtual puro). Esa clase no puede ser instanciada.

    La definición 2 es posible con la herencia virtual: Herencia múltiple de dos clases derivadas


118
2017-11-16 19:36



Pienso en ellos como una forma disciplinada de usar la herencia múltiple, porque finalmente una mezcla es solo otra clase de python que (podría) seguir las convenciones sobre las clases que se llaman mixins.

Mi entendimiento de las convenciones que gobiernan algo que llamarías un Mixin es ese Mixin:

  • agrega métodos pero no variables de instancia (las constantes de clase son correctas)
  • solo hereda de object (en Python)

De esta forma, limita la complejidad potencial de la herencia múltiple y hace que sea razonablemente fácil rastrear el flujo de su programa al limitar dónde debe mirar (en comparación con la herencia múltiple completa). Son similares a los módulos ruby.

Si quiero agregar variables de instancia (con más flexibilidad que la permitida por herencia individual), entonces tiendo a ir por composición.

Habiendo dicho eso, he visto clases llamadas XYZMixin que sí tienen variables de instancia.


26
2017-07-05 14:26



Mixins es un concepto en Programación en el que la clase proporciona funcionalidades, pero no está destinado a ser utilizado para creación de instancias. El objetivo principal de Mixins es proporcionar funcionalidades que son independientes y sería mejor si las mezclas no tienen herencia con otras mixinas y también evitan el estado. En idiomas como Ruby, hay un poco de soporte de lenguaje directo, pero para Python, no existe. Sin embargo, podría usar la herencia de varias clases para ejecutar la funcionalidad provista en Python.

Vi este video http://www.youtube.com/watch?v=v_uKI2NOLEM para entender los conceptos básicos de mixins. Es bastante útil para un principiante entender los conceptos básicos de mixins y cómo funcionan y los problemas que podría enfrentar al implementarlos.

Wikipedia sigue siendo la mejor: http://en.wikipedia.org/wiki/Mixin


19
2018-06-13 05:06



Aconsejo que no se mezclen en el nuevo código de Python, si se puede encontrar de otra manera (como la composición en lugar de la herencia, o simplemente los métodos de parche de monos en sus propias clases) que no es mucho más esfuerzo.

En las clases de estilo antiguo, podría usar mix-ins como una forma de obtener algunos métodos de otra clase. Pero en el mundo de nuevo estilo, todo, incluso el mix-in, hereda de object. Eso significa que cualquier uso de herencia múltiple introduce naturalmente Problemas de MRO.

Hay formas de hacer MRO de herencia múltiple en Python, especialmente la función super (), pero significa que tienes que hacer toda tu jerarquía de clase usando super (), y es mucho más difícil entender el flujo de control.


11
2018-02-10 19:21



¿Qué separa a un mixin de una herencia múltiple? ¿Es solo una cuestión de semántica?

Una mixin es una forma limitada de herencia múltiple. En algunos idiomas, el mecanismo para agregar un mixin a una clase es ligeramente diferente (en términos de sintaxis) al de la herencia.

En el contexto de Python especialmente, una mixin es una clase principal que proporciona funcionalidad a las subclases, pero no está pensada para ser instanciada.

Lo que podría hacer que digas, "eso es solo herencia múltiple, no realmente una mezcla" es si la clase que podría confundirse para una mezcla puede ser instanciada y utilizada, así que de hecho es una diferencia semántica, y muy real.

Ejemplo de Herencia Múltiple

Este ejemplo, de la documentación, es un contador ordenado:

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),)

Subclasifica tanto el Counter y el OrderedDict desde el collections módulo.

Ambos Counter y OrderedDict están destinados a ser instanciados y utilizados por su cuenta. Sin embargo, al subclasificarlos a ambos, podemos tener un contador que se ordena y reutiliza el código en cada objeto.

Esta es una forma poderosa de reutilizar el código, pero también puede ser problemático. Si resulta que hay un error en uno de los objetos, arreglarlo sin cuidado podría crear un error en la subclase.

Ejemplo de un Mixin

Mixins generalmente se promueve como la forma de obtener la reutilización de código sin posibles problemas de acoplamiento que la herencia múltiple cooperativa, como el OrderedCounter, podría tener. Cuando utiliza mixins, utiliza una funcionalidad que no está tan estrechamente unida a los datos.

A diferencia del ejemplo anterior, un mixin no está destinado a ser utilizado solo. Proporciona funcionalidad nueva o diferente.

Por ejemplo, la biblioteca estándar tiene un par de Mixins en el socketserver biblioteca.

Se pueden crear versiones de bifurcación y roscado de cada tipo de servidor   usando estas clases mix-in. Por ejemplo, ThreadingUDPServer es   creado de la siguiente manera:

class ThreadingUDPServer(ThreadingMixIn, UDPServer):
    pass

La clase mix-in es lo primero, ya que anula un método definido en   UDPServer. Establecer los diversos atributos también cambia el comportamiento de   el mecanismo del servidor subyacente.

En este caso, los métodos mixin anulan los métodos en UDPServer definición de objeto para permitir la concurrencia.

El método reemplazado parece ser process_request y también proporciona otro método, process_request_thread. Aquí es de código fuente:

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()

Un ejemplo contruido

Esta es una combinación que es principalmente para fines de demostración: la mayoría de los objetos evolucionarán más allá de la utilidad de esta revisión:

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)
          )

y el uso sería:

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__()

Y el uso:

>>> f1 = Foo('bar')
>>> f2 = Foo()
>>> f1
Foo(foo='bar')
>>> f2
Foo(foo=None)

11
2018-03-25 15:05



Tal vez un par de ejemplos ayuden.

Si está creando una clase y desea que actúe como un diccionario, puede definir todos los diferentes __ __ métodos necesarios. Pero eso es un poco doloroso. Como alternativa, puede definir unos pocos y heredar (además de cualquier otra herencia) de UserDict.DictMixin (trasladado a collections.DictMixin en py3k). Esto tendrá el efecto de definir automáticamente todo el resto de la API del diccionario.

Un segundo ejemplo: el kit de herramientas GUI wxPython le permite hacer controles de lista con varias columnas (como, por ejemplo, la visualización de archivos en el Explorador de Windows). Por defecto, estas listas son bastante básicas. Puede agregar funcionalidades adicionales, como la capacidad de ordenar la lista por una columna en particular haciendo clic en el encabezado de la columna, heredando de ListCtrl y agregando las mezclas apropiadas.


8
2018-02-10 21:27



Creo que ha habido algunas buenas explicaciones aquí, pero quería brindar otra perspectiva.

En Scala, puedes hacer mixins como se ha descrito aquí, pero lo que es muy interesante es que los mixins están realmente 'fusionados' para crear un nuevo tipo de clase para heredar. Básicamente, no se hereda de múltiples clases / mixins, sino que se genera un nuevo tipo de clase con todas las propiedades de la mezcla para heredar. Esto tiene sentido ya que Scala está basado en la JVM donde la herencia múltiple no es actualmente compatible (a partir de Java 8). Este tipo de clase mixin, por cierto, es un tipo especial llamado Rasgo en Scala.

Se insinúa en la forma en que se define una clase:     clase NewClass extiende FirstMixin con SecondMixin con ThirdMixin     ...

No estoy seguro si el intérprete CPython hace lo mismo (mixin class-composition) pero no me sorprendería. Además, viniendo de un fondo de C ++, no llamaría un ABC o 'interfaz' equivalente a un mixin; es un concepto similar pero divergente en el uso y la implementación.


8
2017-12-26 05:54



Tal vez un ejemplo de ruby ​​puede ayudar:

Puede incluir la mezcla Comparable y define una función "<=>(other)", el mixin proporciona todas esas funciones:

<(other)
>(other)
==(other)
<=(other)
>=(other)
between?(other)

Lo hace invocando <=>(other) y devolviendo el resultado correcto.

"instance <=> other" devuelve 0 si ambos objetos son iguales, menos de 0 si instance es mayor que other y más de 0 si other es más grande.


6
2018-02-10 19:08