Comprender Python super() con métodos __init__() [duplicado]
¿ Por qué se super()
utiliza?
¿Existe alguna diferencia entre usar Base.__init__
y super().__init__
?
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()
le permite evitar hacer referencia explícita a la clase base, lo cual puede ser bueno. Pero la principal ventaja viene con la herencia múltiple, donde pueden suceder todo tipo de cosas divertidas . Consulte los documentos estándar en super si aún no lo ha hecho.
Tenga en cuenta que la sintaxis cambió en Python 3.0 : puede simplemente decir super().__init__()
en lugar de super(ChildB, self).__init__()
cuál en mi opinión es bastante mejor. Los documentos estándar también hacen referencia a una guía de usosuper()
que es bastante explicativa.
estoy tratando de entender
super()
La razón que usamos super
es para que las clases secundarias que puedan estar usando herencia múltiple cooperativa llamen a la siguiente función de clase principal correcta en el Orden de resolución del método (MRO).
En Python 3, podemos llamarlo así:
class ChildB(Base):
def __init__(self):
super().__init__()
En Python 2, debíamos llamar super
así con el nombre de la clase que define y self
, pero evitaremos esto de ahora en adelante porque es redundante, más lento (debido a las búsquedas de nombres) y más detallado (así que actualice su Python si ¡Aún no lo he hecho!):
super(ChildB, self).__init__()
Sin super, su capacidad para utilizar la herencia múltiple está limitada porque cablea la llamada del siguiente padre:
Base.__init__(self) # Avoid this.
Lo explico más 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().__init__()
La principal diferencia en este código es que ChildB
obtienes una capa de indirección en __init__
with super
, que usa la clase en la que está definido para determinar la siguiente clase __init__
a buscar en el MRO.
Ilustro esta diferencia en una respuesta a la pregunta canónica: ¿Cómo usar 'super' en Python? , que demuestra la inyección de dependencia y la herencia múltiple cooperativa .
Si Python no tuvierasuper
Aquí hay un código que en realidad es muy equivalente a super
(cómo se implementa en C, menos algunos comportamientos de verificación y respaldo, y se traduce a Python):
class ChildB(Base):
def __init__(self):
mro = type(self).mro()
check_next = mro.index(ChildB) + 1 # next 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 Python nativo:
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 llamar al siguiente método adecuado en el orden de resolución del método.
¿Cómo hace super esto en Python 3 sin que se le diga explícitamente desde qué clase e instancia del método se llamó?
Obtiene el marco de la pila de llamada y encuentra la clase (almacenada implícitamente como una variable local libre, __class__
lo que hace que la función de llamada sea un cierre sobre la clase) y el primer argumento de esa función, que debe ser la instancia o clase que le informa cuál Orden de resolución del método (MRO) a utilizar.
Dado que requiere ese primer argumento para el MRO, usarlo super
con métodos estáticos es imposible ya que no tienen acceso al MRO de la clase desde la que se llaman .
Críticas de otras respuestas:
super() te permite evitar hacer referencia explícita a la clase base, lo cual puede ser bueno. . Pero la principal ventaja viene con la herencia múltiple, donde pueden suceder todo tipo de cosas divertidas. Consulte los documentos estándar en super si aún no lo ha hecho.
Es bastante sencillo y no nos dice mucho, pero el objetivo super
no es evitar escribir la clase principal. El objetivo es garantizar que se llame al siguiente método en línea en el orden de resolución del método (MRO). Esto adquiere importancia 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().__init__()
Y creemos una dependencia que queremos que se llame en honor al Niño:
class UserDependency(Base):
def __init__(self):
print("UserDependency init'ed")
super().__init__()
Ahora recuerda, ChildB
usa super, ChildA
no:
class UserA(ChildA, UserDependency):
def __init__(self):
print("UserA init'ed")
super().__init__()
class UserB(ChildB, UserDependency):
def __init__(self):
print("UserB init'ed")
super().__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
de hecho, llama a UserDependency porque ChildB
invoca super
:
>>> 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 debes hacer lo siguiente, como sugiere otra respuesta, ya que definitivamente obtendrás errores al subclasificar ChildB:
super(self.__class__, self).__init__() # DON'T DO THIS! EVER.
(Esa respuesta no es inteligente ni particularmente interesante, pero a pesar de las críticas directas en los comentarios y más de 17 votos negativos, el que respondió persistió en sugerirla hasta que un amable editor solucionó su problema).
Explicación: Usarlo self.__class__
como sustituto del nombre de la clase super()
conducirá a la recursividad. super
busquemos el siguiente padre en el MRO (consulte la primera sección de esta respuesta) para clases secundarias. Si le dice super
que estamos en el método de la instancia secundaria, buscará el siguiente método en línea (probablemente este), lo que dará como resultado la recursividad, lo que probablemente provocará una falla lógica (en el ejemplo del respondedor, lo hace) o cuando RuntimeError
la profundidad de la recursividad se supera.
>>> 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 "<stdin>", line 1, in <module>
File "<stdin>", line 3, in __init__
TypeError: __init__() missing 2 required positional arguments: 'width' and 'height'
Afortunadamente, el nuevo método de llamada de Python 3 super()
sin argumentos nos permite evitar este problema.
Se ha observado que en Python 3.0+ puedes usar
super().__init__()
para realizar su llamada, que es concisa y no requiere que haga referencia explícita a los nombres principales O de clase, lo cual puede ser útil. Solo quiero agregar que para Python 2.7 o inferior, algunas personas implementan un comportamiento que no distingue el nombre escribiendo self.__class__
en lugar del nombre de la clase, es decir
super(self.__class__, self).__init__() # DON'T DO THIS!
SIN EMBARGO, esto interrumpe las llamadas a super
cualquier clase que herede de su 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 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
usando mSquare = Square('a', 10,10)
, Python llama al constructor Rectangle
porque no le he dado Square
su propio constructor. Sin embargo, en el constructor for Rectangle
, la llamada super(self.__class__,self)
devolverá la superclase de mSquare
, por lo que vuelve a llamar al constructor for Rectangle
. Así es como ocurre el bucle infinito, como lo mencionó @S_C. En este caso, cuando ejecuto super(...).__init__()
estoy llamando al constructor Rectangle
pero como no le doy argumentos, obtendré un error.