subclases - super clases python
¿Cómo funciona el super() de Python con herencia múltiple? (8)
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 ).
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
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".
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
.
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.
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