subclases multiple herencia ejercicio clases python multiple-inheritance

multiple - ¿Cómo funciona el super() de Python con herencia múltiple?



subclases python (11)

En general

Asumiendo que todo desciende del object (usted está solo si no lo hace), Python calcula un orden de resolución de método (MRO) basado en su árbol de herencia de clase. El MRO satisface 3 propiedades:

  • Los niños de una clase vienen antes que sus padres.
  • Padres de izquierda vienen antes que padres de derecha
  • Una clase solo aparece una vez en el MRO

Si no existe tal ordenamiento, errores de Python. El funcionamiento interno de esto es una Linerización C3 de la ascendencia de las clases. Lea todo sobre esto aquí: https://www.python.org/download/releases/2.3/mro/

Por lo tanto, en los dos ejemplos siguientes, es:

  1. Niño
  2. Izquierda
  3. Derecha
  4. Padre

Cuando se llama a un método, la primera aparición de ese método en el MRO es la que se llama. Cualquier clase que no implemente ese método se omite. Cualquier llamada a super dentro de ese método llamará a la siguiente aparición de ese método en el MRO. En consecuencia, importa tanto el orden en el que coloca las clases en herencia como el lugar en el que coloca las llamadas a super en los métodos.

Con super primero en cada metodo

class Parent(object): def __init__(self): super(Parent, self).__init__() print "parent" class Left(Parent): def __init__(self): super(Left, self).__init__() print "left" class Right(Parent): def __init__(self): super(Right, self).__init__() print "right" class Child(Left, Right): def __init__(self): super(Child, self).__init__() print "child"

Child() Salidas:

parent right left child

Con super último en cada método.

class Parent(object): def __init__(self): print "parent" super(Parent, self).__init__() class Left(Parent): def __init__(self): print "left" super(Left, self).__init__() class Right(Parent): def __init__(self): print "right" super(Right, self).__init__() class Child(Left, Right): def __init__(self): print "child" super(Child, self).__init__()

Child() Salidas:

child left right parent

Soy bastante nuevo en la programación orientada a objetos de Python y tengo problemas para entender la función super() (nuevas clases de estilo), especialmente cuando se trata de herencia múltiple.

Por ejemplo si tienes algo como:

class First(object): def __init__(self): print "first" class Second(object): def __init__(self): print "second" class Third(First, Second): def __init__(self): super(Third, self).__init__() print "that''s it"

Lo que no entiendo es: ¿la Third() clase heredará ambos métodos de construcción? En caso afirmativo, ¿cuál se ejecutará con super () y por qué?

¿Y si quieres correr el otro? Sé que tiene algo que ver con el orden de resolución del método Python ( MRO ).


Acerca del comentario de @calfzhou , puedes usar, como de costumbre, **kwargs :

Ejemplo de ejecución en línea

class A(object): def __init__(self, a, *args, **kwargs): print("A", a) class B(A): def __init__(self, b, *args, **kwargs): super(B, self).__init__(*args, **kwargs) print("B", b) class A1(A): def __init__(self, a1, *args, **kwargs): super(A1, self).__init__(*args, **kwargs) print("A1", a1) class B1(A1, B): def __init__(self, b1, *args, **kwargs): super(B1, self).__init__(*args, **kwargs) print("B1", b1) B1(a1=6, b1=5, b="hello", a=None)

Resultado:

A None B hello A1 6 B1 5

También puedes usarlos de forma posicional:

B1(5, 6, b="hello", a=None)

Pero hay que recordar el MRO, es realmente confuso.

Puedo ser un poco molesto, pero me di cuenta de que las personas se olvidaban cada vez de usar *args y **kwargs cuando anulan un método, mientras que es una de las pocas aplicaciones realmente útiles y sensatas de estas "variables mágicas".


Así es como resolví el problema de tener herencia múltiple con diferentes variables para la inicialización y tener varios MixIns con la misma función de llamada. Tuve que agregar explícitamente variables a las Kwargs pasadas ** y agregar una interfaz MixIn para ser un punto final para superclasificaciones.

Aquí A es una clase base extensible y B y C son clases MixIn que proporcionan la función f . Tanto A como B esperan el parámetro v en su __init__ y C espera w . La función f toma un parámetro y . Q hereda de las tres clases. MixInF es la interfaz mixin para B y C

class A(object): def __init__(self, v, *args, **kwargs): print "A:init:v[{0}]".format(v) kwargs[''v'']=v super(A, self).__init__(*args, **kwargs) self.v = v class MixInF(object): def __init__(self, *args, **kwargs): print "IObject:init" def f(self, y): print "IObject:y[{0}]".format(y) class B(MixInF): def __init__(self, v, *args, **kwargs): print "B:init:v[{0}]".format(v) kwargs[''v'']=v super(B, self).__init__(*args, **kwargs) self.v = v def f(self, y): print "B:f:v[{0}]:y[{1}]".format(self.v, y) super(B, self).f(y) class C(MixInF): def __init__(self, w, *args, **kwargs): print "C:init:w[{0}]".format(w) kwargs[''w'']=w super(C, self).__init__(*args, **kwargs) self.w = w def f(self, y): print "C:f:w[{0}]:y[{1}]".format(self.w, y) super(C, self).f(y) class Q(C,B,A): def __init__(self, v, w): super(Q, self).__init__(v=v, w=w) def f(self, y): print "Q:f:y[{0}]".format(y) super(Q, self).f(y)


Entiendo que esto no responde directamente a la pregunta super() , pero creo que es lo suficientemente relevante para compartir.

También hay una forma de llamar directamente a cada clase heredada:

class First(object): def __init__(self): print ''1'' class Second(object): def __init__(self): print ''2'' class Third(First, Second): def __init__(self): Second.__init__(self)

Solo tenga en cuenta que si lo hace de esta manera, tendrá que llamar a cada uno manualmente, ya que estoy bastante seguro de que no se llamará a __init__() First .


Esto se conoce como el problema del diamante , la página tiene una entrada en Python, pero en resumen, Python llamará a los métodos de la superclase de izquierda a derecha.


Esto se detalla con una cantidad razonable de detalles por el mismo Guido en su publicación de blog Orden de resolución de métodos (incluidos dos intentos anteriores).

En tu ejemplo, Third() llamará First.__init__ . Python busca cada atributo en los padres de la clase según se enumeran de izquierda a derecha. En este caso estamos buscando __init__ . Por lo tanto, si usted define

class Third(First, Second): ...

Python comenzará mirando First , y, si First no tiene el atributo, entonces verá Second .

Esta situación se vuelve más compleja cuando la herencia comienza a cruzar caminos (por ejemplo, si First hereda de Second ). Lea el enlace anterior para obtener más detalles, pero, en pocas palabras, Python intentará mantener el orden en el que aparece cada clase en la lista de herencia, comenzando con la propia clase secundaria.

Así, por ejemplo, si tuvieras:

class First(object): def __init__(self): print "first" class Second(First): def __init__(self): print "second" class Third(First): def __init__(self): print "third" class Fourth(Second, Third): def __init__(self): super(Fourth, self).__init__() print "that''s it"

el MRO sería [Fourth, Second, Third, First].

Por cierto: si Python no puede encontrar un orden de resolución de método coherente, generará una excepción, en lugar de recurrir a un comportamiento que podría sorprender al usuario.

Editado para agregar el ejemplo de un MRO ambiguo:

class First(object): def __init__(self): print "first" class Second(First): def __init__(self): print "second" class Third(First, Second): def __init__(self): print "third"

¿El MRO de Third debe ser [First, Second] o [Second, First] ? No hay una expectativa obvia, y Python generará un error:

TypeError: Error when calling the metaclass bases Cannot create a consistent method resolution order (MRO) for bases Second, First

Edit: veo a varias personas argumentando que los ejemplos anteriores carecen de llamadas super() , así que permítanme explicar: el punto de los ejemplos es mostrar cómo se construye el MRO. No están diseñados para imprimir "primero / nsegundo / tercero" o lo que sea. Puede, y debería, por supuesto, jugar con el ejemplo, agregar llamadas super() , ver qué sucede y obtener una comprensión más profunda del modelo de herencia de Python. Pero mi objetivo aquí es mantenerlo simple y mostrar cómo se construye el MRO. Y está construido como expliqué:

>>> Fourth.__mro__ (<class ''__main__.Fourth''>, <class ''__main__.Second''>, <class ''__main__.Third''>, <class ''__main__.First''>, <type ''object''>)


Me gustaría agregar a lo que @Visionscaper dice en la parte superior:

Third --> First --> object --> Second --> object

En este caso, el intérprete no filtra la clase de objeto porque está duplicada, sino porque la segunda aparece en una posición de cabecera y no aparece en la posición de cola en un subconjunto de jerarquía. Mientras que el objeto solo aparece en las posiciones de cola y no se considera una posición fuerte en el algoritmo C3 para determinar la prioridad.

La linealización (mro) de una clase C, L (C), es la

  • la clase c
  • más la fusión de
    • linealización de sus padres P1, P2, .. = L (P1, P2, ...) y
    • La lista de sus padres P1, P2, ..

La fusión lineal se realiza seleccionando las clases comunes que aparecen como el encabezado de las listas y no la cola, ya que el orden importa (se aclarará a continuación)

La linealización de Third se puede calcular de la siguiente manera:

L(O) := [O] // the linearization(mro) of O(object), because O has no parents L(First) := [First] + merge(L(O), [O]) = [First] + merge([O], [O]) = [First, O] // Similarly, L(Second) := [Second, O] L(Third) := [Third] + merge(L(First), L(Second), [First, Second]) = [Third] + merge([First, O], [Second, O], [First, Second]) // class First is a good candidate for the first merge step, because it only appears as the head of the first and last lists // class O is not a good candidate for the next merge step, because it also appears in the tails of list 1 and 2, = [Third, First] + merge([O], [Second, O], [Second]) // class Second is a good candidate for the second merge step, because it appears as the head of the list 2 and 3 = [Third, First, Second] + merge([O], [O]) = [Third, First, Second, O]

Así para una implementación super () en el siguiente código:

class First(object): def __init__(self): super(First, self).__init__() print "first" class Second(object): def __init__(self): super(Second, self).__init__() print "second" class Third(First, Second): def __init__(self): super(Third, self).__init__() print "that''s it"

Se vuelve obvio cómo se resolverá este método.

Third.__init__() ---> First.__init__() ---> Second.__init__() ---> Object.__init__() ---> returns ---> Second.__init__() - prints "second" - returns ---> First.__init__() - prints "first" - returns ---> Third.__init__() - prints "that''s it"


Otro punto aún no cubierto es el paso de parámetros para la inicialización de clases. Dado que el destino de super depende de la subclase, la única manera de pasar parámetros es empaquetarlos todos juntos. Luego, tenga cuidado de no tener el mismo nombre de parámetro con diferentes significados.

Ejemplo:

class A(object): def __init__(self, **kwargs): print(''A.__init__'') super().__init__() class B(A): def __init__(self, **kwargs): print(''B.__init__ {}''.format(kwargs[''x''])) super().__init__(**kwargs) class C(A): def __init__(self, **kwargs): print(''C.__init__ with {}, {}''.format(kwargs[''a''], kwargs[''b''])) super().__init__(**kwargs) class D(B, C): # MRO=D, B, C, A def __init__(self): print(''D.__init__'') super().__init__(a=1, b=2, x=3) print(D.mro()) D()

da:

[<class ''__main__.D''>, <class ''__main__.B''>, <class ''__main__.C''>, <class ''__main__.A''>, <class ''object''>] D.__init__ B.__init__ 3 C.__init__ with 1, 2 A.__init__

Llamar a la súper clase __init__ directamente a una asignación más directa de parámetros es tentador, pero falla si hay alguna super llamada en una súper clase y / o se cambia el MRO y la clase A puede llamarse varias veces, dependiendo de la implementación.

Para concluir: la herencia cooperativa y los parámetros super y específicos para la inicialización no funcionan juntos muy bien.


Quería elaborar la respuesta un poco sin vida porque cuando empecé a leer sobre cómo usar super () en una jerarquía de herencia múltiple en Python, no lo obtuve de inmediato.

Lo que debe comprender es que super(MyClass, self).__init__() proporciona el siguiente método __init__ según el algoritmo de Orden de resolución de métodos (MRO) utilizado en el contexto de la jerarquía de herencia completa .

Esta última parte es crucial para entender. Consideremos el ejemplo de nuevo:

class First(object): def __init__(self): super(First, self).__init__() print "first" class Second(object): def __init__(self): super(Second, self).__init__() print "second" class Third(First, Second): def __init__(self): super(Third, self).__init__() print "that''s it"

De acuerdo con este artículo sobre el Método de Resolución de Orden por Guido van Rossum, el orden para resolver __init__ se calcula (antes de Python 2.3) usando un "primer recorrido de izquierda a derecha de profundidad":

Third --> First --> object --> Second --> object

Después de eliminar todos los duplicados, excepto el último, obtenemos:

Third --> First --> Second --> object

Entonces, sigamos lo que sucede cuando creamos una instancia de la Third clase, por ejemplo, x = Third() .

  1. Según MRO __init__ de Tercero se llama primero.

  2. A continuación, según el MRO, dentro del método __init__ super(Third, self).__init__() resuelve el método __init__ de First, al que se llama.

  3. Dentro de __init__ de First super(First, self).__init__() llama __init__ de Second, porque eso es lo que dicta el MRO.

  4. Dentro de __init__ de Second super(Second, self).__init__() llama al __init__ de objeto, lo que equivale a nada. Después de eso se imprime el "segundo" .

  5. Después de super(First, self).__init__() completado, se imprime "first" .

  6. Después de super(Third, self).__init__() completado, se imprime "eso es todo" .

Esto detalla por qué la instanciación de Third () da como resultado:

>>> x = Third() second first that''s it

El algoritmo MRO se ha mejorado desde Python 2.3 en adelante para funcionar bien en casos complejos, pero supongo que el uso del "primer recorrido de izquierda a derecha" + "eliminando duplicados esperados para el último" todavía funciona en la mayoría de los casos (por favor comentar si este no es el caso). ¡Asegúrate de leer la publicación del blog de Guido!


Su código, y las otras respuestas, son todos con errores. Faltan las llamadas super() en las dos primeras clases que se requieren para que funcionen las subclases cooperativas.

Aquí hay una versión fija del código:

class First(object): def __init__(self): super(First, self).__init__() print("first") class Second(object): def __init__(self): super(Second, self).__init__() print("second") class Third(First, Second): def __init__(self): super(Third, self).__init__() print("third")

La llamada super() encuentra el siguiente método en el MRO en cada paso, por lo que First y Second también deben tenerlo, de lo contrario, la ejecución se detiene al final de Second.__init__() .

Esto es lo que obtengo:

>>> Third() second first third


class First(object): def __init__(self, a): print "first", a super(First, self).__init__(20) class Second(object): def __init__(self, a): print "second", a super(Second, self).__init__() class Third(First, Second): def __init__(self): super(Third, self).__init__(10) print "that''s it" t = Third()

La salida es

first 10 second 20 that''s it

Call to Third () localiza el init definido en Third. Y llamar a super en esa rutina invoca a init definido en First. MRO = [Primero, Segundo]. Ahora, la llamada a super en init definida en Primera continuará buscando MRO y encontrará init definida en Segunda, y cualquier llamada a super golpeará el objeto predeterminado predeterminado. Espero que este ejemplo aclare el concepto.

Si no llamas super de First. La cadena se detiene y obtendrás la siguiente salida.

first 10 that''s it