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:
- Niño
- Izquierda
- Derecha
- 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
:
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 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()
.
Según MRO
__init__
de Tercero se llama primero.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.Dentro de
__init__
de Firstsuper(First, self).__init__()
llama__init__
de Second, porque eso es lo que dicta el MRO.Dentro de
__init__
de Secondsuper(Second, self).__init__()
llama al__init__
de objeto, lo que equivale a nada. Después de eso se imprime el "segundo" .Después de
super(First, self).__init__()
completado, se imprime "first" .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