multiple - python oop inheritance




¿Qué es una mezcla y por qué son útiles? (10)

¿Qué separa una mezcla de la herencia múltiple? ¿Es sólo una cuestión de semántica?

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

Especialmente en el contexto de Python, una mezcla es una clase principal que proporciona funcionalidad a las subclases, pero no está diseñada para ser instanciada.

Lo que podría hacer que usted diga, "eso es solo herencia múltiple, no es realmente una mezcla" es si la clase que podría confundirse con una mezcla puede realmente ser instanciada y utilizada, por lo que es una diferencia semántica y muy real.

Ejemplo de herencia múltiple

Este ejemplo, de la documentación , es un 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),)

OrderedDict tanto el Counter como el OrderedDict del módulo de collections .

Tanto Counter como OrderedDict están diseñados para ser instanciados y utilizados por sí mismos. Sin embargo, al subclasificarlos a ambos, podemos tener un contador que está ordenado 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, corregirlo sin cuidado podría crear un error en la subclase.

Ejemplo de un Mixin

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

A diferencia del ejemplo anterior, un mixin no está diseñado para usarse solo. Proporciona funcionalidad nueva o diferente.

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

Las versiones de bifurcación y subprocesamiento de cada tipo de servidor se pueden crear utilizando estas clases de mezcla. Por ejemplo, ThreadingUDPServer se crea de la siguiente manera:

class ThreadingUDPServer(ThreadingMixIn, UDPServer):
    pass

La clase de mezcla viene primero, ya que anula un método definido en UDPServer. La configuración de los diversos atributos también cambia el comportamiento del mecanismo del servidor subyacente.

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

El método anulado parece ser process_request y también proporciona otro método, process_request_thread . Aquí está del 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 elaborado

Esta es una mezcla que es principalmente para fines de demostración: la mayoría de los objetos evolucionarán más allá de la utilidad de esta reproducció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 uso:

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

En " Programming Python ", Mark Lutz menciona "mixins". Soy de un fondo de C / C ++ / C # y no he escuchado el término antes. ¿Qué es una mezcla?

Leyendo entre líneas en este ejemplo (al que he vinculado porque es bastante largo), supongo que es un caso de uso de herencia múltiple para extender una clase en lugar de subclasificación "adecuada". ¿Es esto correcto?

¿Por qué querría hacer eso en lugar de poner la nueva funcionalidad en una subclase? En este sentido, ¿por qué sería mejor un enfoque mixto / herencia múltiple que el uso de la composición?

¿Qué separa una mezcla de la herencia múltiple? ¿Es sólo una cuestión de semántica?


Acabo de usar un mixin de python para implementar pruebas unitarias para milters de python. Normalmente, un milter habla con un MTA, lo que dificulta las pruebas de unidad. La mezcla de prueba anula los métodos que hablan con el MTA y, en su lugar, crea un entorno simulado controlado por casos de prueba.

Entonces, tomas una aplicación de milter sin modificar, como spfmilter, y mezcla en TestBase, como esto:

class TestMilter(TestBase,spfmilter.spfMilter):
  def __init__(self):
    TestBase.__init__(self)
    spfmilter.config = spfmilter.Config()
    spfmilter.config.access_file = 'test/access.db'
    spfmilter.spfMilter.__init__(self)

Luego, use TestMilter en los casos de prueba para la aplicación milter:

def testPass(self):
  milter = TestMilter()
  rc = milter.connect('mail.example.com',ip='192.0.2.1')
  self.assertEqual(rc,Milter.CONTINUE)
  rc = milter.feedMsg('test1',sender='[email protected]')
  self.assertEqual(rc,Milter.CONTINUE)
  milter.close()

http://pymilter.cvs.sourceforge.net/viewvc/pymilter/pymilter/Milter/test.py?revision=1.6&view=markup


Esta respuesta apunta a explicar los mixins con ejemplos que son:

  • autocontenido : breve, 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 hilo de Python .

También se considerará la cuestión controvertida:

¿Es necesaria la herencia múltiple o no para caracterizar una mezcla?

Definiciones

Todavía tengo que ver una cita de una fuente "autorizada" que dice claramente qué es una mezcla en Python.

He visto 2 definiciones posibles de un mixin (si han de considerarse como diferentes de otros conceptos similares, como las clases básicas abstractas), y las personas no están del todo de acuerdo en cuál es la correcta.

El consenso puede variar entre diferentes idiomas.

Definición 1: sin herencia múltiple

Una mezcla 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 está destinada a ser instanciada, sino que sirve como una clase base. De lo contrario, la instancia tendría métodos a los que no se puede 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é esto es necesario. Sin embargo, en la práctica, muchos mixins útiles no tienen datos, y las clases base sin datos son más fáciles 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 en particular podría haberse logrado a través del decorador functools.total_ordering() , pero el juego aquí fue 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 usa un método que no define, y ese método está destinado a ser implementado por otra clase base , no por el derivado como en la Definición 1.

El término clase de mezcla se refiere a las clases base que están destinadas a usarse en ese patrón de diseño (¿TODO aquellos que usan el método o aquellos que lo implementan?)

No es fácil decidir si una clase dada es una combinación o no: el método podría implementarse en la clase derivada, en cuyo caso volvemos a la Definición 1. Debe tener en cuenta las intenciones del autor.

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

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

Apariciones de Python autorizadas

En la documentación oficial para colecciones.abc, la documentación utiliza explícitamente el término Métodos de mezcla .

Afirma que si una clase:

  • implementa __next__
  • hereda de una sola clase Iterator

entonces la clase obtiene un método de __iter__ gratis.

Por lo tanto, al menos en este punto de la documentación, la mezcla 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 , que sugiere claramente que las clases como Set e Iterator se pueden llamar clases Mixin.

En otros idiomas

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

  • C ++: un método que no se implementa 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 herencia virtual: herencia múltiple de dos clases derivadas


Leí que tienes un fondo de AC #. Por lo tanto, un buen punto de partida podría ser una implementación mixta para .NET.

Es posible que desee revisar el proyecto codeplex en http://remix.codeplex.com/

Mire el enlace del simposio de lang.net para obtener una descripción general. Todavía hay más por venir en la documentación en la página de codeplex.

saludos a Stefan


No es un ejemplo de Python, pero en el lenguaje de programación D, el término mixin se usa para referirse a una construcción que se usa de la misma forma; añadiendo un montón de cosas a una clase.

En D (que por cierto no hace MI) esto se hace insertando una plantilla (piense en macros seguras y sintácticamente conscientes y estará cerca) en un ámbito. Esto permite una sola línea de código en una clase, estructura, función, módulo o lo que sea para expandir a cualquier número de declaraciones.


OP mencionó que él / ella nunca escuchó acerca de la mezcla en C ++, tal vez es porque se les llama Curiously Recurring Template Pattern (CRTP) en C ++. Además, @Ciro Santilli mencionó que la mezcla se implementa a través de la clase base abstracta en C ++. Si bien la clase base abstracta se puede usar para implementar la mezcla, es una exageración, ya que la funcionalidad de la función virtual en tiempo de ejecución puede lograrse utilizando una plantilla en tiempo de compilación sin la sobrecarga de la búsqueda de tablas virtuales en tiempo de ejecución.

El patrón CRTP se describe en detalle here

He convertido el ejemplo de python en la respuesta de @Ciro Santilli en C ++ usando la clase de plantilla a continuación:

#include <iostream>
#include <assert.h>

template <class T>
class ComparableMixin {
public:
    bool operator !=(ComparableMixin &other) {
        return ~(*static_cast<T*>(this) == static_cast<T&>(other));
    }
    bool operator <(ComparableMixin &other) {
        return ((*(this) != other) && (*static_cast<T*>(this) <= static_cast<T&>(other)));
    }
    bool operator >(ComparableMixin &other) {
        return ~(*static_cast<T*>(this) <= static_cast<T&>(other));
    }
    bool operator >=(ComparableMixin &other) {
        return ((*static_cast<T*>(this) == static_cast<T&>(other)) || (*(this) > other));
    }
};

class Integer: public ComparableMixin<Integer> {
public:
 Integer(int i) {
     this->i = i;
 }
 int i;
 bool operator <=(Integer &other) {
     return (this->i <= other.i);
 }
 bool operator ==(Integer &other) {
     return (this->i == other.i);
 }
};

int main() {

    Integer i(0) ;
    Integer j(1) ;

    assert (i < j );
    assert (i != j);
    assert (j >  i);
    assert (j >= i);

    return 0;
}

Primero, debe tener en cuenta que los mixins solo existen en idiomas de herencia múltiple. No puedes hacer una mezcla en Java o C #.

Básicamente, una mezcla es un tipo de 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 tenga que implementar realmente porque ya está implementada; simplemente hereda de él y se beneficia de su funcionalidad.

Los mixins son típicamente estrechos en su alcance y no están destinados a ser extendidos.

[editar - por qué:]

Supongo que debería abordar el porqué, ya que lo pediste. El gran beneficio es que no tienes que hacerlo tú mismo una y otra vez. En C #, el lugar más grande donde una mezcla podría beneficiarse podría ser el patrón de Disposición . Cada vez que implementas IDisposable, casi siempre quieres seguir el mismo patrón, pero terminas escribiendo y reescribiendo el mismo código básico con pequeñas variaciones. Si hubiera una mezcla de eliminación extensible, podría ahorrarse muchos tipos adicionales de escritura.

[edit 2 - para responder a tus otras preguntas]

¿Qué separa una mezcla de la herencia múltiple? ¿Es sólo 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 una mezcla es crear un tipo que se pueda "mezclar" con cualquier otro tipo a través de la herencia sin afectar al tipo hereditario y al mismo tiempo ofrecer alguna funcionalidad beneficiosa para ese tipo.

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

Personalmente no uso los mixins, ya que me desarrollo principalmente en un lenguaje que no los admite, por lo que me está costando mucho dar un ejemplo decente que simplemente proporcione ese "¡ahah!" momento para ti Pero lo intentaré de nuevo. Voy a usar un ejemplo que está diseñado (la mayoría de los idiomas ya proporcionan la característica de una forma u otra), pero eso, con suerte, explicará cómo se supone que se crean y usan los mixins. Aquí va:

Supongamos que tiene un tipo que desea poder serializar hacia y desde XML. Desea que el tipo proporcione un método "ToXML" que devuelva una cadena que contenga un fragmento XML con los valores de datos del tipo, y un "FromXML" que permita al tipo reconstruir sus valores de datos de un fragmento XML en una cadena. Nuevamente, este es un ejemplo artificial, por lo que quizás use una secuencia de archivos o una clase de Escritor XML de la biblioteca de tiempo de ejecución de su idioma ... lo que sea. El punto es que desea serializar su objeto a XML y recuperar un nuevo objeto de XML.

El otro punto importante en este ejemplo es que desea hacer esto de una manera genérica. No desea tener que implementar un método "ToXML" y "FromXML" para cada tipo que desee serializar, quiere algunos medios genéricos para asegurarse de que su tipo hará esto y simplemente funciona. Quieres reutilizar el código.

Si su idioma lo admite, puede crear la mezcla XmlSerializable para hacer su trabajo por usted. Este tipo implementaría los métodos ToXML y FromXML. Utilizando algún mecanismo que no sea importante para el ejemplo, sería capaz de recopilar todos los datos necesarios de cualquier tipo con el que se mezcle 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, debe tener cualquier tipo que deba ser serializado a XML heredado de XmlSerializable. Cuando necesitara serializar o deserializar ese tipo, simplemente llamaría a ToXML o FromXML. De hecho, dado que XmlSerializable es un tipo completo y polimórfico, posiblemente podría crear un serializador de documentos que no sepa nada sobre su tipo original, aceptando solo, por ejemplo, una variedad de tipos XmlSerializable.

Ahora imagine utilizar este escenario para otras cosas, como crear una mezcla que garantice que cada clase que la mezcle registre cada llamada de método o una combinación que ofrezca transaccionalidad al tipo que la mezcla. La lista puede seguir y seguir.

Si solo piensas en una mezcla como un tipo de base pequeña diseñada para agregar una pequeña cantidad de funcionalidad a un tipo sin afectar ese tipo, entonces estás de oro.

Ojalá. :)


Quizás un par de ejemplos ayuden.

Si está creando una clase y desea que actúe como un diccionario, puede definir todos los diversos métodos __ __ necesarios. Pero eso es un poco de dolor. Como alternativa, solo puede definir algunos 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 realizar controles de lista con varias columnas (como, por ejemplo, la visualización del archivo en el Explorador de Windows). Por defecto, estas listas son bastante básicas. Puede agregar funcionalidad adicional, 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 los mixins apropiados.


Tal vez un ejemplo de ruby ​​puede ayudar:

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

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

Hace esto invocando <=>(other) y devolviendo el resultado correcto.

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


Un mixin es un tipo especial de herencia múltiple. Hay dos situaciones principales en las que se utilizan mixins:

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

Para un ejemplo del número uno, considere el sistema de solicitud y respuesta de werkzeug . Puedo hacer un objeto de solicitud antiguo simple diciendo:

from werkzeug import BaseRequest

class Request(BaseRequest):
    pass

Si quisiera agregar aceptar el soporte de encabezado, haría que

from werkzeug import BaseRequest, AcceptMixin

class Request(AcceptMixin, BaseRequest):
    pass

Si quisiera hacer un objeto de solicitud que admita encabezados, etags, autenticación y soporte de 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 mixtas no se hicieron por sí solas. En la herencia múltiple más tradicional, la AuthenticationMixin (por ejemplo) probablemente sería algo más como Authenticator . Es decir, la clase probablemente estaría diseñada para valerse por sí misma.





mixins