method - ¿Qué hace ''super'' en Python?
super class self__ init__ python (6)
¿Cual es la diferencia?
SomeBaseClass.__init__(self)
significa llamar a SomeBaseClass
de __init__
. mientras
super(Child, self).__init__()
significa llamar a un __init__
encuadernado de la clase padre que sigue al Child
en la Orden de resolución de métodos (MRO) de la instancia.
Si la instancia es una subclase de Niño, puede haber un padre diferente que viene a continuación en el MRO.
Explicado simplemente
Cuando escribes una clase, quieres que otras clases puedan usarla. super()
facilita que otras clases usen la clase que estás escribiendo.
Como dice Bob Martin, una buena arquitectura le permite posponer la toma de decisiones el mayor tiempo posible.
super()
puede habilitar ese tipo de arquitectura.
Cuando otra clase subclasifica la clase que escribiste, también podría ser heredada de otras clases. Y esas clases podrían tener un __init__
que viene después de este __init__
basado en el orden de las clases para la resolución del método.
Sin el super
, probablemente codificarías el padre de la clase que estás escribiendo (como lo hace el ejemplo). Esto significaría que no llamaría al siguiente __init__
en el MRO y, por lo tanto, no podría reutilizar el código que __init__
.
Si está escribiendo su propio código para uso personal, es posible que no le importe esta distinción. Pero si quiere que otros usen su código, usar super
es una cosa que permite una mayor flexibilidad para los usuarios del código.
Python 2 contra 3
Esto funciona en Python 2 y 3:
super(Child, self).__init__()
Esto solo funciona en Python 3:
super().__init__()
Funciona sin argumentos moviéndose hacia arriba en el marco de la pila y obteniendo el primer argumento del método (generalmente self
para un método de instancia o cls
para un método de clase, pero podría ser otros nombres) y encontrando la clase (por ejemplo, Child
) en el variables libres (se busca con el nombre __class__
como una variable de cierre libre en el método).
Prefiero demostrar la forma de uso super
compatible, pero si solo usas Python 3, puedes llamarlo sin argumentos.
Indirección con compatibilidad hacia adelante
¿Qué te da? Para la herencia única, los ejemplos de la pregunta son prácticamente idénticos desde el punto de vista del análisis estático. Sin embargo, el uso de super
le da una capa de direccionamiento indirecto con compatibilidad hacia adelante.
La compatibilidad hacia adelante es muy importante para los desarrolladores experimentados. Desea que su código siga funcionando con cambios mínimos a medida que lo modifique. Cuando mira su historial de revisiones, desea ver exactamente qué cambió cuándo.
Puede comenzar con una herencia única, pero si decide agregar otra clase base, solo tiene que cambiar la línea con las bases - si las bases cambian en una clase de la que hereda (digamos que se agrega una mezcla) cambiaría nada en esta clase Particularmente en Python 2, obtener los argumentos para super
y los argumentos correctos del método puede ser difícil. Si sabe que está usando super
correctamente con herencia única, eso hace que la depuración sea menos difícil de seguir adelante.
Inyección de dependencia
Otras personas pueden usar su código e inyectar a los padres en la resolución del método:
class SomeBaseClass(object):
def __init__(self):
print(''SomeBaseClass.__init__(self) called'')
class UnsuperChild(SomeBaseClass):
def __init__(self):
print(''UnsuperChild.__init__(self) called'')
SomeBaseClass.__init__(self)
class SuperChild(SomeBaseClass):
def __init__(self):
print(''SuperChild.__init__(self) called'')
super(SuperChild, self).__init__()
Supongamos que agrega otra clase a su objeto y desea inyectar una clase entre Foo y Bar (para probar o por alguna otra razón):
class InjectMe(SomeBaseClass):
def __init__(self):
print(''InjectMe.__init__(self) called'')
super(InjectMe, self).__init__()
class UnsuperInjector(UnsuperChild, InjectMe): pass
class SuperInjector(SuperChild, InjectMe): pass
El uso del niño un-super no puede inyectar la dependencia porque el niño que estás usando tiene el método codificado para que se llame después de su propio:
>>> o = UnsuperInjector()
UnsuperChild.__init__(self) called
SomeBaseClass.__init__(self) called
Sin embargo, la clase con el hijo que usa super
puede inyectar correctamente la dependencia:
>>> o2 = SuperInjector()
SuperChild.__init__(self) called
InjectMe.__init__(self) called
SomeBaseClass.__init__(self) called
Dirigiendo un comentario
¿Por qué en el mundo esto sería útil?
Python linealiza un árbol de herencia complicado a través del algoritmo de linealización C3 para crear un Orden de resolución de métodos (MRO).
Queremos que los métodos se busquen en ese orden .
Para que un método definido en un padre para encontrar el siguiente en ese orden sin super
, tendría que
- obtener el mro del tipo de instancia
- Busca el tipo que define el método.
- Encuentra el siguiente tipo con el método.
- Enlaza ese método y llámalo con los argumentos esperados.
El
UnsuperChild
no debería tener acceso aInjectMe
. ¿Por qué no es la conclusión "siempre evitar el uso desuper
"? ¿Que me estoy perdiendo aqui?
El UnsuperChild
no tiene acceso a InjectMe
. El UnsuperInjector
es el que tiene acceso a InjectMe
y, sin embargo, no puede llamar al método de esa clase desde el método que hereda de UnsuperChild
.
Ambas clases secundarias intentan llamar a un método con el mismo nombre que viene a continuación en la MRO, que podría ser otra clase de la que no tenía conocimiento cuando se creó.
El que no tiene códigos super
rígidos de su método principal, por lo tanto, ha restringido el comportamiento de su método y las subclases no pueden inyectar funcionalidad en la cadena de llamadas.
El que tiene super
tiene mayor flexibilidad. La cadena de llamadas para los métodos puede ser interceptada y la funcionalidad inyectada.
Es posible que no necesite esa funcionalidad, pero puede que los subclases de su código.
Conclusión
Siempre use super
para hacer referencia a la clase padre en lugar de codificarla.
Lo que pretende es hacer referencia a la clase padre que está en la siguiente línea, no específicamente de la que se ve heredado el hijo.
No usar super
puede poner restricciones innecesarias a los usuarios de su código.
Cuál es la diferencia entre:
class Child(SomeBaseClass):
def __init__(self):
super(Child, self).__init__()
y:
class Child(SomeBaseClass):
def __init__(self):
SomeBaseClass.__init__(self)
He visto que los super
se usan mucho en clases con una sola herencia. Puedo ver por qué lo usaría en herencia múltiple, pero no tengo claro cuáles son las ventajas de usarlo en este tipo de situación.
¿No asume todo esto que la clase base es una clase de nuevo estilo?
class A:
def __init__(self):
print("A.__init__()")
class B(A):
def __init__(self):
print("B.__init__()")
super(B, self).__init__()
No funcionará en Python 2. La class A
debe ser de estilo nuevo, es decir: class A(object)
Al llamar a super()
para resolver la versión de un método de clase, método de instancia o método estático de un padre, queremos pasar la clase actual en cuyo ámbito estamos como primer argumento, para indicar el alcance del padre que estamos tratando de resolver. y, como segundo argumento, el objeto de interés para indicar a qué objeto estamos tratando de aplicar ese alcance.
Considere una jerarquía de clases A
, B
y C
donde cada clase es la principal de la que la sigue, y a
, b
, c
instancias respectivas de cada una.
super(B, b)
# resolves to the scope of B''s parent i.e. A
# and applies that scope to b, as if b was an instance of A
super(C, c)
# resolves to the scope of C''s parent i.e. B
# and applies that scope to c
super(B, c)
# resolves to the scope of B''s parent i.e. A
# and applies that scope to c
Usando super
con un método estático
por ejemplo, usando super()
desde el __new__()
class A(object):
def __new__(cls, *a, **kw):
# ...
# whatever you want to specialize or override here
# ...
return super(A, cls).__new__(cls, *a, **kw)
Explicación:
1- Aunque es habitual que __new__()
tome como primera referencia a la clase que llama, no se implementa en Python como un método de clase, sino más bien como un método estático. Es decir, una referencia a una clase debe pasarse explícitamente como el primer argumento cuando se llama a __new__()
directamente:
# if you defined this
class A(object):
def __new__(cls):
pass
# calling this would raise a TypeError due to the missing argument
A.__new__()
# whereas this would be fine
A.__new__(A)
2- al llamar a super()
para llegar a la clase principal, pasamos la clase secundaria A
como primer argumento, luego pasamos una referencia al objeto de interés, en este caso es la referencia de clase que se pasó cuando A.__new__(cls)
fue llamado. En la mayoría de los casos, también pasa a ser una referencia a la clase infantil. En algunas situaciones puede que no lo sea, por ejemplo, en el caso de herencias de múltiples generaciones.
super(A, cls)
3- dado que como regla general, __new__()
es un método estático, super(A, cls).__new__
también devolverá un método estático y se le deben proporcionar todos los argumentos explícitamente, incluida la referencia al objeto de interés, en este caso cls
.
super(A, cls).__new__(cls, *a, **kw)
4- haciendo lo mismo sin super
class A(object):
def __new__(cls, *a, **kw):
# ...
# whatever you want to specialize or override here
# ...
return object.__new__(cls, *a, **kw)
Usando super
con un método de instancia
por ejemplo, usando super()
desde __init__()
class A(object):
def __init__(self, *a, **kw):
# ...
# you make some changes here
# ...
super(A, self).__init__(*a, **kw)
Explicación:
1- __init__
es un método de instancia, lo que significa que toma como primer argumento una referencia a una instancia. Cuando se llama directamente desde la instancia, la referencia se pasa implícitamente, es decir, no es necesario que la especifique:
# you try calling `__init__()` from the class without specifying an instance
# and a TypeError is raised due to the expected but missing reference
A.__init__() # TypeError ...
# you create an instance
a = A()
# you call `__init__()` from that instance and it works
a.__init__()
# you can also call `__init__()` with the class and explicitly pass the instance
A.__init__(a)
2- al llamar a super()
dentro de __init__()
pasamos la clase secundaria como primer argumento y el objeto de interés como segundo argumento, que en general es una referencia a una instancia de la clase secundaria.
super(A, self)
3- La llamada super(A, self)
devuelve un proxy que resolverá el alcance y lo aplicará a self
como si ahora fuera una instancia de la clase padre. Llamemos a ese proxy s
. Como __init__()
es un método de instancia, la llamada s.__init__(...)
pasará implícitamente una referencia de self
como el primer argumento al __init__()
del padre.
4- Para hacer lo mismo sin super
, necesitamos pasar una referencia a una instancia explícitamente a la versión principal de __init__()
.
class A(object):
def __init__(self, *a, **kw):
# ...
# you make some changes here
# ...
object.__init__(self, *a, **kw)
Usando super
con un método de clase
class A(object):
@classmethod
def alternate_constructor(cls, *a, **kw):
print "A.alternate_constructor called"
return cls(*a, **kw)
class B(A):
@classmethod
def alternate_constructor(cls, *a, **kw):
# ...
# whatever you want to specialize or override here
# ...
print "B.alternate_constructor called"
return super(B, cls).alternate_constructor(*a, **kw)
Explicación:
1- Se puede llamar a un método de clase directamente desde la clase y toma como primer parámetro una referencia a la clase.
# calling directly from the class is fine,
# a reference to the class is passed implicitly
a = A.alternate_constructor()
b = B.alternate_constructor()
2- cuando se llama a super()
dentro de un método de clase para resolver su versión primaria, queremos pasar la clase secundaria actual como el primer argumento para indicar a qué ámbito de padres estamos tratando de resolver, y el objeto de interés como el segundo argumento para indicar a qué objeto queremos aplicar ese ámbito, que en general es una referencia a la propia clase hija o una de sus subclases.
super(B, cls_or_subcls)
3- La llamada super(B, cls)
resuelve en el alcance de A
y se aplica a cls
. Dado que alternate_constructor()
es un método de clase, la llamada super(B, cls).alternate_constructor(...)
pasará implícitamente una referencia de cls
como el primer argumento de la versión de A de alternate_constructor()
super(B, cls).alternate_constructor()
4- para hacer lo mismo sin usar super()
, necesitaría obtener una referencia a la versión A.alternate_constructor()
de A.alternate_constructor()
(es decir, la versión explícita de la función). Simplemente hacer esto no funcionaría:
class B(A):
@classmethod
def alternate_constructor(cls, *a, **kw):
# ...
# whatever you want to specialize or override here
# ...
print "B.alternate_constructor called"
return A.alternate_constructor(cls, *a, **kw)
Lo anterior no funcionaría porque el método A.alternate_constructor()
toma una referencia implícita a A
como su primer argumento. Los cls
se pasan aquí serían su segundo argumento.
class B(A):
@classmethod
def alternate_constructor(cls, *a, **kw):
# ...
# whatever you want to specialize or override here
# ...
print "B.alternate_constructor called"
# first we get a reference to the unbound
# `A.alternate_constructor` function
unbound_func = A.alternate_constructor.im_func
# now we call it and pass our own `cls` as its first argument
return unbound_func(cls, *a, **kw)
Había jugado un poco con super()
y había reconocido que podemos cambiar el orden de las llamadas.
Por ejemplo, tenemos la siguiente estructura jerárquica:
A
/ /
B C
/ /
D
En este caso, MRO de D será (solo para Python 3):
In [26]: D.__mro__
Out[26]: (__main__.D, __main__.B, __main__.C, __main__.A, object)
Vamos a crear una clase donde super()
llamadas super()
después de la ejecución del método.
In [23]: class A(object): # or with Python 3 can define class A:
...: def __init__(self):
...: print("I''m from A")
...:
...: class B(A):
...: def __init__(self):
...: print("I''m from B")
...: super().__init__()
...:
...: class C(A):
...: def __init__(self):
...: print("I''m from C")
...: super().__init__()
...:
...: class D(B, C):
...: def __init__(self):
...: print("I''m from D")
...: super().__init__()
...: d = D()
...:
I''m from D
I''m from B
I''m from C
I''m from A
A
/ ⇖
B ⇒ C
⇖ /
D
Así que podemos ver que el orden de resolución es el mismo que en MRO. Pero cuando llamamos super()
al comienzo del método:
In [21]: class A(object): # or class A:
...: def __init__(self):
...: print("I''m from A")
...:
...: class B(A):
...: def __init__(self):
...: super().__init__() # or super(B, self).__init_()
...: print("I''m from B")
...:
...: class C(A):
...: def __init__(self):
...: super().__init__()
...: print("I''m from C")
...:
...: class D(B, C):
...: def __init__(self):
...: super().__init__()
...: print("I''m from D")
...: d = D()
...:
I''m from A
I''m from C
I''m from B
I''m from D
Tenemos un orden diferente, se invierte un orden de la tupla MRO.
A
/ ⇘
B ⇐ C
⇘ /
D
Para una lectura adicional recomendaría las siguientes respuestas:
- Ejemplo de linealización C3 con super (una gran jerarquía)
- Cambios importantes de comportamiento entre las clases de estilo antiguo y nuevo.
- La historia interna de las clases de nuevo estilo
Los beneficios de super()
en herencia simple son mínimos: en su mayoría, no tiene que codificar el nombre de la clase base en cada método que usa sus métodos primarios.
Sin embargo, es casi imposible usar la herencia múltiple sin super()
. Esto incluye expresiones comunes como mixins, interfaces, clases abstractas, etc. Esto se extiende al código que más tarde amplía el suyo. Si alguien más tarde deseara escribir una clase que extendiera Child
y un mixin, su código no funcionaría correctamente.
class Child(SomeBaseClass): def __init__(self): SomeBaseClass.__init__(self)
Esto es bastante fácil de entender.
class Child(SomeBaseClass): def __init__(self): super(Child, self).__init__()
Ok, ¿qué pasa ahora si usas super(Child,self)
?
Cuando se crea una instancia secundaria, su MRO (Orden de resolución de métodos) está en el orden de (secundaria, objeto, clase de objeto) en función de la herencia. (Supongamos que SomeBaseClass no tiene otros padres a excepción del objeto predeterminado)
Al pasar Child, self
, super
búsquedas en el MRO de la instancia del self
, y devolver el objeto proxy al lado de Child, en este caso es SomeBaseClass, este objeto invoca el método __init__ de SomeBaseClass. En otras palabras, si es super(SomeBaseClass,self)
, el objeto proxy que devuelve super
sería object
Para la herencia múltiple, el MRO podría contener muchas clases, por lo que, básicamente, super
permite decidir dónde desea comenzar a buscar en el MRO.