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

Soy bastante nuevo en la progtwigció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 ).

Esto se detalla con una cantidad razonable de detalles por parte del propio Guido en su orden de resolución de métodos de la publicación del blog (incluidos dos bashs 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 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" 

¿Debería ser el MRO de Third [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 objective aquí es mantenerlo simple y mostrar cómo se construye el MRO. Y está construido como expliqué:

 >>> Fourth.__mro__ (, , , , ) 

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 

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

Lo que debe comprender es que super(MyClass, self).__init__() proporciona el siguiente método __init__ acuerdo con el algoritmo de Orden de resolución de métodos (MRO) usado 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 se llama primero a __init__ de Tercero.

  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 creo que el uso del “+ primero 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!

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 es así como resolví el problema de tener herencia múltiple con diferentes variables para la inicialización y tener múltiples 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

  • Cuaderno de notas de este código
  • Github Repo con código de ejemplo
 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)
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 .

En general

Suponiendo 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 

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 la gente se olvidaba cada vez de usar *args y **kwargs cuando **kwargs un método, mientras que es una de las pocas aplicaciones realmente útiles y sensatas de estas “variables mágicas”.

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:

 [, , , , ] 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.

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

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" 

En learningpythonthehardway aprendo algo llamado super () una función incorporada si no me equivoco. Llamar a la función super () puede ayudar a que la herencia pase a través del padre y los “hermanos” y te ayude a ver más claro. Todavía soy un principiante, pero me encanta compartir mi experiencia al usar este super () en python2.7.

Si ha leído los comentarios en esta página, escuchará acerca de la Orden de resolución de métodos (MRO), el método es la función que escribió, el MRO utilizará el esquema de profundidad primero a la izquierda a la derecha para buscar y ejecutar. Puedes hacer más investigación sobre eso.

Añadiendo la función super ()

 super(First, self).__init__() #example for class First. 

Puede conectar varias instancias y ‘familias’ con super (), agregando cada una de ellas. Y ejecutará los métodos, los analizará y se asegurará de que no se los pierda. Sin embargo, agregarlos antes o después hace una diferencia, sabrá si ha hecho el aprendizaje y el ejercicio 44. ¡Que comience la diversión!

Tomando el ejemplo a continuación, puede copiar y pegar e intentar ejecutarlo:

 class First(object): def __init__(self): print("first") class Second(First): def __init__(self): print("second (before)") super(Second, self).__init__() print("second (after)") class Third(First): def __init__(self): print("third (before)") super(Third, self).__init__() print("third (after)") class Fourth(First): def __init__(self): print("fourth (before)") super(Fourth, self).__init__() print("fourth (after)") class Fifth(Second, Third, Fourth): def __init__(self): print("fifth (before)") super(Fifth, self).__init__() print("fifth (after)") Fifth() 

¿Cómo funciona? La instancia de la quinta () será así. Cada paso va de clase en clase donde se agrega la función super.

 1.) print("fifth (before)") 2.) super()>[Second, Third, Fourth] (Left to right) 3.) print("second (before)") 4.) super()> First (First is the Parent which inherit from object) 

Se encontró al padre y se va a continuar en Tercero y Cuarto !!

 5.) print("third (before)") 6.) super()> First (Parent class) 7.) print ("Fourth (before)") 8.) super()> First (Parent class) 

Ahora se ha accedido a todas las clases con super ()! La clase principal se ha encontrado y ejecutado y ahora continúa desajustando la función en las herencias para finalizar los códigos.

 9.) print("first") (Parent) 10.) print ("Fourth (after)") (Class Fourth un-box) 11.) print("third (after)") (Class Third un-box) 12.) print("second (after)") (Class Second un-box) 13.) print("fifth (after)") (Class Fifth un-box) 14.) Fifth() executed 

El resultado del progtwig anterior:

 fifth (before) second (before third (before) fourth (before) first fourth (after) third (after) second (after) fifth (after) 

Para mí, agregar super () me permite ver con más claridad cómo Python ejecutaría mi encoding y asegurarme de que la herencia pueda acceder al método que pretendía.