Entendiendo Python super () con __init __ () métodos

Estoy tratando de entender el uso de super() . Por lo que parece, se pueden crear ambas clases secundarias, bien.

Tengo curiosidad por saber acerca de la diferencia real entre las siguientes 2 clases para niños.

 class Base(object): def __init__(self): print "Base created" class ChildA(Base): def __init__(self): Base.__init__(self) class ChildB(Base): def __init__(self): super(ChildB, self).__init__() ChildA() ChildB() 

super() permite evitar referirse a la clase base explícitamente, lo que puede ser bueno. Pero la principal ventaja viene con la herencia múltiple, donde pueden ocurrir todo tipo de cosas divertidas . Ver los documentos estándar en super si aún no lo ha hecho.

Tenga en cuenta que la syntax cambió en Python 3.0 : simplemente puede decir super().__init__() lugar de super(ChildB, self).__init__() que IMO es bastante más agradable. Los documentos estándar también se refieren a una guía para usar super () que es bastante explicativa.

Estoy tratando de entender super()

La razón por la que usamos super es para que las clases secundarias que pueden estar usando herencia múltiple cooperativa llamen a la siguiente función de clase principal en la Orden de resolución de métodos (MRO).

En Python 3, podemos llamarlo así:

 class ChildB(Base): def __init__(self): super().__init__() 

En Python 2, debemos usarlo así:

  super(ChildB, self).__init__() 

Sin super, está limitado en su capacidad de usar herencia múltiple:

  Base.__init__(self) # Avoid this. 

Te explico más abajo a continuación.

“¿Qué diferencia hay realmente en este código ?:”

 class ChildA(Base): def __init__(self): Base.__init__(self) class ChildB(Base): def __init__(self): super(ChildB, self).__init__() # super().__init__() # you can call super like this in Python 3! 

La principal diferencia en este código es que obtiene una capa de __init__ indirecto en __init__ con super , que utiliza la clase actual para determinar el __init__ la siguiente clase para buscar en el MRO.

Ilustro esta diferencia en una respuesta a la pregunta canónica, ¿Cómo usar ‘super’ en Python? , lo que demuestra la dependency injection y la herencia múltiple cooperativa .

Si Python no tuviera super

Aquí hay un código que en realidad es casi equivalente a super (cómo se implementa en C, menos algunos comportamientos de comprobación y reserva, y se traduce a Python):

 class ChildB(Base): def __init__(self): mro = type(self).mro() # Get the Method Resolution Order. check_next = mro.index(ChildB) + 1 # Start looking after *this* class. while check_next < len(mro): next_class = mro[check_next] if '__init__' in next_class.__dict__: next_class.__init__(self) break check_next += 1 

Escrito un poco más como nativo de Python:

 class ChildB(Base): def __init__(self): mro = type(self).mro() for next_class in mro[mro.index(ChildB) + 1:]: # slice to end if hasattr(next_class, '__init__'): next_class.__init__(self) break 

Si no tuviéramos el super objeto, tendríamos que escribir este código manual en todas partes (¡o recrearlo!) Para asegurarnos de que llamamos el siguiente método apropiado en la Orden de resolución de métodos!

¿Cómo hace super en esto en Python 3 sin que se le diga explícitamente de qué clase y instancia desde el método se llamó?

Obtiene el marco de la stack de llamada y encuentra la clase (almacenada implícitamente como una variable libre local, __class__ , haciendo que la función de llamada se cierre sobre la clase) y el primer argumento de esa función, que debe ser la instancia o clase que la informa. qué Orden de Resolución de Método (MRO) utilizar.

Ya que requiere ese primer argumento para el MRO, es imposible usar super método super con métodos estáticos .

Críticas de otras respuestas:

super () le permite evitar referirse a la clase base explícitamente, lo que puede ser bueno. . Pero la principal ventaja viene con la herencia múltiple, donde pueden ocurrir todo tipo de cosas divertidas. Ver los documentos estándar en super si aún no lo ha hecho.

Es más bien manual y no nos dice mucho, pero el objective de super no es evitar escribir en la clase principal. El punto es asegurar que se llame al siguiente método en línea en el orden de resolución de métodos (MRO). Esto se vuelve importante en la herencia múltiple.

Te lo explicaré aquí.

 class Base(object): def __init__(self): print("Base init'ed") class ChildA(Base): def __init__(self): print("ChildA init'ed") Base.__init__(self) class ChildB(Base): def __init__(self): print("ChildB init'ed") super(ChildB, self).__init__() 

Y vamos a crear una dependencia que queremos que sea llamada después del niño:

 class UserDependency(Base): def __init__(self): print("UserDependency init'ed") super(UserDependency, self).__init__() 

Ahora recuerde, ChildB usa super, ChildA no:

 class UserA(ChildA, UserDependency): def __init__(self): print("UserA init'ed") super(UserA, self).__init__() class UserB(ChildB, UserDependency): def __init__(self): print("UserB init'ed") super(UserB, self).__init__() 

Y UserA no llama al método UserDependency:

 >>> UserA() UserA init'ed ChildA init'ed Base init'ed <__main__.UserA object at 0x0000000003403BA8> 

Pero UserB , porque ChildB usa super , hace !:

 >>> UserB() UserB init'ed ChildB init'ed UserDependency init'ed Base init'ed <__main__.UserB object at 0x0000000003403438> 

Crítica por otra respuesta.

En ninguna circunstancia debe hacer lo siguiente, que sugiere otra respuesta, ya que definitivamente obtendrá errores cuando subclasifique ChildB:

  super(self.__class__, self).__init__() # Don't do this. Ever. 

(Esa respuesta no es inteligente o particularmente interesante, pero a pesar de las críticas directas en los comentarios y más de 17 votos a la baja, el respondedor insistió en sugerirlo hasta que un amable editor resolvió su problema).

Explicación: Esa respuesta sugería llamar super como esto:

 super(self.__class__, self).__init__() 

Esto está completamente mal. super nos permite buscar al siguiente padre en el MRO (vea la primera sección de esta respuesta) para las clases de niños. Si le dice a super que estamos en el método de la instancia secundaria, buscará el siguiente método en la línea (probablemente este) que resulte en una recursión, probablemente causando un fallo lógico (en el ejemplo del que responde, si lo hace) o un RuntimeError cuando el Se excede la profundidad de la recursión.

 >>> class Polygon(object): ... def __init__(self, id): ... self.id = id ... >>> class Rectangle(Polygon): ... def __init__(self, id, width, height): ... super(self.__class__, self).__init__(id) ... self.shape = (width, height) ... >>> class Square(Rectangle): ... pass ... >>> Square('a', 10, 10) Traceback (most recent call last): File "", line 1, in  File "", line 3, in __init__ TypeError: __init__() missing 2 required positional arguments: 'width' and 'height' 

Se ha observado que en Python 3.0+ puede usar

super().__init__()

para hacer su llamada, que es concisa y no requiere que haga referencia explícita a los nombres de clase del padre O, lo que puede ser útil. Solo quiero agregar que para Python 2.7 o inferior, es posible obtener este comportamiento no sensible al nombre al escribir self.__class__ lugar del nombre de la clase, es decir

 super(self.__class__, self).__init__() 

SIN EMBARGO, esto rompe las llamadas a super para cualquier clase que herede de tu clase, donde self.__class__ podría devolver una clase secundaria. Por ejemplo:

 class Polygon(object): def __init__(self, id): self.id = id class Rectangle(Polygon): def __init__(self, id, width, height): super(self.__class__, self).__init__(id) self.shape = (width, height) class Square(Rectangle): pass 

Aquí tengo una clase Square , que es una subclase de Rectangle . Digamos que no quiero escribir un constructor separado para Square porque el constructor para Rectangle es lo suficientemente bueno, pero por alguna razón quiero implementar un Square para poder volver a implementar algún otro método.

Cuando creo un Square utilizando mSquare = Square('a', 10,10) , Python llama al constructor para el Rectangle porque no le he dado a Square su propio constructor. Sin embargo, en el constructor de Rectangle , la llamada super(self.__class__,self) devolverá la superclase de mSquare , por lo que vuelve a llamar al constructor para Rectangle . Así es como sucede el bucle infinito, como lo mencionó @S_C. En este caso, cuando ejecuto super(...).__init__() estoy llamando al constructor para Rectangle pero como no le doy argumentos, obtendré un error.

Super no tiene efectos secundarios

 Base = ChildB Base() 

funciona como se espera

 Base = ChildA Base() 

se mete en la recursión infinita.

Solo un aviso … con Python 2.7, y creo que desde que se introdujo super() en la versión 2.2, solo se puede llamar super() si uno de los padres hereda de una clase que eventualmente hereda el object ( clases de nuevo estilo ).

Personalmente, en cuanto al código de Python 2.7, continuaré usando BaseClassName.__init__(self, args) hasta que obtenga la ventaja de usar super() .

No hay, en realidad. super() mira la siguiente clase en el MRO (orden de resolución de métodos, al que se accede con cls.__mro__ ) para llamar a los métodos. Simplemente llamando a la base __init__ llama a la base __init__ . Como sucede, el MRO tiene exactamente un elemento: la base. Así que realmente estás haciendo exactamente lo mismo, pero de una manera más agradable con super() (especialmente si luego obtienes herencia múltiple).

La principal diferencia es que ChildA.__init__ incondicionalmente llamará Base.__init__ mientras que ChildB.__init__ llamará __init__ en cualquier clase que sea ancestro de ChildB en self línea de antepasados ​​de sí mismo (que puede diferir de lo que usted espera).

Si agrega una ClassC que usa herencia múltiple:

 class Mixin(Base): def __init__(self): print "Mixin stuff" super(Mixin, self).__init__() class ChildC(ChildB, Mixin): # Mixin is now between ChildB and Base pass ChildC() help(ChildC) # shows that the the Method Resolution Order is ChildC->ChildB->Mixin->Base 

entonces Base ya no es el padre de ChildB para ChildC instancias de ChildC . Ahora super(ChildB, self) apuntará a Mixin si self es una instancia de ChildC .

Has insertado Mixin entre ChildB y Base . Y puedes aprovecharla con super()

Entonces, si está diseñado sus clases para que puedan usarse en un escenario de herencia múltiple cooperativa, use super porque realmente no sabe quién va a ser el antepasado en el tiempo de ejecución.

El super acompañante y el video que acompaña a Pycon 2015 lo explican bastante bien.