¿Qué hace 'super' en Python? - diferencia entre super().__init__() y la superclase explícita __init__()

Resuelto user25785 asked hace 15 años • 11 respuestas

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 superque se usa bastante en clases con una sola herencia. Puedo ver por qué lo usarías en herencia múltiple, pero no tengo claro cuáles son las ventajas de usarlo en este tipo de situación.


Esta pregunta trata sobre detalles técnicos de implementación y la distinción entre diferentes formas de acceder al __init__método de la clase base. Para cerrar preguntas duplicadas en las que a OP simplemente le falta una superllamada y pregunta por qué los atributos de la clase base no están disponibles, utilice ¿ Por qué mis instancias de subclase no contienen los atributos de la clase base (provocando un AttributeError cuando intento usarlos)? ? en cambio.

user25785 avatar Oct 22 '08 01:10 user25785
Aceptado

¿Cual es la diferencia?

SomeBaseClass.__init__(self) 

significa llamar SomeBaseClass's __init__. mientras

super().__init__()

significa llamar a un límite __init__desde la clase principal que sigue SomeBaseClassa la clase secundaria (la que define este método) en el Orden de resolución de métodos (MRO) de la instancia.

Si la instancia es una subclase de esta clase secundaria, puede haber un padre diferente 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 arquitectura permite posponer la toma de decisiones el mayor tiempo posible.

super()puede permitir ese tipo de arquitectura.

Cuando otra clase crea una subclase de la clase que usted escribió, también podría estar heredando de otras clases. Y esas clases podrían tener una __init__que venga después de esto __init__según el orden de las clases para la resolución del método.

Sin superusted, probablemente codificaría el padre de la clase que está 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 contiene.

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, usarlo superes algo que permite una mayor flexibilidad para los usuarios del código.

Pitón 2 contra 3

Esto funciona en Python 2 y 3:

super(Child, self).__init__()

Esto sólo 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 selfpara un método de instancia o clspara un método de clase, pero podrían tener otros nombres) y buscando la clase (por ejemplo, Child) en las variables libres ( se busca con el nombre __class__como variable de cierre libre en el método).

Solía ​​preferir demostrar la forma de uso compatible con super, pero ahora que Python 2 está en gran medida obsoleto, demostraré la forma de hacer las cosas de Python 3, es decir, llamar supersin 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 superle brinda una capa de direccionamiento indirecto con compatibilidad hacia adelante.

La compatibilidad con versiones posteriores es muy importante para los desarrolladores experimentados. Quiere que su código siga funcionando con cambios mínimos a medida que lo cambia. Cuando miras tu historial de revisiones, quieres ver exactamente qué cambió y cuándo.

Puede comenzar con 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 un mixin), cambiaría nada en esta clase.

En Python 2, obtener los argumentos correctos supery los argumentos del método correcto puede ser un poco confuso, por lo que sugiero usar el método exclusivo de Python 3 para llamarlo.

Si sabe que está utilizando supercorrectamente la herencia única, la depuración será menos difícil en el futuro.

Inyección de dependencia

Otras personas pueden usar su código e insertar 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().__init__()

Supongamos que agrega otra clase a su objeto y desea inyectar una clase entre Foo y Bar (para pruebas o por algún otro motivo):

class InjectMe(SomeBaseClass):
    def __init__(self):
        print('InjectMe.__init__(self) called')
        super().__init__()

class UnsuperInjector(UnsuperChild, InjectMe): pass

class SuperInjector(SuperChild, InjectMe): pass

El uso del hijo no-super no logra inyectar la dependencia porque el hijo que estás usando ha codificado el método que se llamará después del suyo:

>>> o = UnsuperInjector()
UnsuperChild.__init__(self) called
SomeBaseClass.__init__(self) called

Sin embargo, la clase con el niño que usa superpuede inyectar correctamente la dependencia:

>>> o2 = SuperInjector()
SuperChild.__init__(self) called
InjectMe.__init__(self) called
SomeBaseClass.__init__(self) called

Dirigir un comentario

¿Por qué sería útil esto?

Python linealiza un árbol de herencia complicado mediante el algoritmo de linealización C3 para crear un orden de resolución de método (MRO).

Queremos que los métodos se busquen en ese orden .

Para que un método definido en un padre encuentre 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. vincular ese método y llamarlo con los argumentos esperados

No UnsuperChilddeberían tener acceso a InjectMe. ¿Por qué la conclusión no es "Evite siempre usar super"? ¿Que me estoy perdiendo aqui?

El UnsuperChildno tiene acceso a InjectMe. Es el UnsuperInjectorque tiene acceso InjectMey aún no puede llamar al método de esa clase desde el método del que hereda UnsuperChild.

Ambas clases Child tienen la intención de llamar a un método con el mismo nombre que aparece a continuación en el MRO, que podría ser otra clase que no conocía cuando se creó.

El que no supercodifica el método de su padre; 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 de los métodos se puede interceptar y se puede inyectar funcionalidad.

Es posible que no necesite esa funcionalidad, pero los subclasificadores de su código sí pueden.

Conclusión

Úselo siempre superpara hacer referencia a la clase principal en lugar de codificarla.

Lo que pretende es hacer referencia a la clase principal que es la siguiente en la línea, no específicamente a aquella de la que hereda el hijo.

No usarlo superpuede imponer restricciones innecesarias a los usuarios de su código.

Russia Must Remove Putin avatar Nov 02 '2015 00:11 Russia Must Remove Putin

Los beneficios de super()la herencia única son mínimos; principalmente, no es necesario codificar el nombre de la clase base en cada método que utiliza sus métodos principales.

Sin embargo, es casi imposible utilizar la herencia múltiple sin super(). Esto incluye modismos comunes como mixins, interfaces, clases abstractas, etc. Esto se extiende al código que luego extiende el suyo. Si alguien más tarde quisiera escribir una clase extendida Childy un mixin, su código no funcionaría correctamente.

John Millikin avatar Oct 21 '2008 18:10 John Millikin

Jugué un poco con super()y reconocí 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á (sólo para Python 3):

In [26]: D.__mro__
Out[26]: (__main__.D, __main__.B, __main__.C, __main__.A, object)

Creemos una clase donde se realicen super()llamadas 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

Entonces 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, es un orden inverso de la tupla MRO.

    A
   / ⇘
  B ⇐ C
   ⇘ /
    D 

Para lecturas adicionales, recomendaría las siguientes respuestas:

  1. Ejemplo de linealización C3 con super (una gran jerarquía)
  2. Cambios de comportamiento importantes entre las clases de estilo antiguas y nuevas
  3. La historia interna de las clases de nuevo estilo
eirenik0 avatar Dec 29 '2016 17:12 eirenik0