¿Qué hace ‘super’ en Python?

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.

Los beneficios de super() en herencia simple son mínimos: en su mayoría, no es necesario que escriba el nombre de la clase base en cada método que use 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.

¿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() hace que sea más fácil para otras clases usar la clase que estás escribiendo.

Como dice Bob Martin, una buena architecture le permite posponer la toma de decisiones el mayor tiempo posible.

super() puede habilitar ese tipo de architecture.

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 al 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 desea 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 stack 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 simple, 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 proporciona 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, hacer que los argumentos sean super y los argumentos de método correctos puedan ser difíciles. Si sabe que está utilizando super correctamente con herencia única, eso hace que la depuración sea menos difícil de seguir.

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

  1. obtener el mro del tipo de instancia
  2. Busca el tipo que define el método.
  3. Encuentra el siguiente tipo con el método.
  4. Enlaza ese método y llámalo con los argumentos esperados.

El UnsuperChild no debería tener acceso a InjectMe . ¿Por qué no es la conclusión “siempre evitar el uso de super “? ¿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 es posible 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.

¿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)

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 de jerarquía:

  A / \ BC \ / 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:

  1. Ejemplo de linealización C3 con super (una gran jerarquía)
  2. Cambios importantes de comportamiento entre clases de estilo antiguo y nuevo.
  3. La historia interna de las clases de nuevo estilo

Cuando se llama 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 scope 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 scope.

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 ie 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 ie B # and applies that scope to c super(B, c) # resolves to the scope of B's parent ie 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 primer parámetro una 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 al llamar 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 scope 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- Un método de clase se puede llamar 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 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 scope 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) 
 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) se encuentra en el orden de (secundaria, objeto de SomeBaseClass, objeto) según 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 super retorna 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 iniciar la búsqueda en el MRO.