herencia - Entendiendo Python super() con__init__() métodos
herencia python super (7)
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, estamos obligados a 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 adelante 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 inyección de dependencia 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 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 que llamamos el siguiente método apropiado en la Orden de resolución de métodos!
¿Cómo hace super 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 pila 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
con métodos estáticos .
Críticas de otras respuestas:
super () le permite evitar referirse explícitamente a la clase base, lo que puede ser agradable. . Pero la principal ventaja viene con la herencia múltiple, donde pueden ocurrir todo tipo de cosas divertidas. Consulte la documentación estándar en super si aún no lo ha hecho.
Es más bien manual y no nos dice mucho, pero el objetivo 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 del método (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 ni 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 error 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 "<stdin>", line 1, in <module>
File "<stdin>", line 3, in __init__
TypeError: __init__() missing 2 required positional arguments: ''width'' and ''height''
Esta pregunta ya tiene una respuesta aquí:
- ¿Qué hace ''super'' en Python? 6 respuestas
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()
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 del self
(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 post considerado y el video que acompaña a Pycon 2015 lo explican bastante bien.
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__
. Da la casualidad de que 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).
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 insensible al nombre escribiendo 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
usando 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.
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()
.
Super no tiene efectos secundarios
Base = ChildB
Base()
funciona como se espera
Base = ChildA
Base()
entra en recursión infinita.
super()
permite evitar referirse explícitamente a la clase base, lo que puede ser agradable. Pero la principal ventaja viene con la herencia múltiple, donde pueden ocurrir todo tipo de cosas divertidas . Consulte la documentación estándar en super si aún no lo ha hecho.
Tenga en cuenta que la sintaxis cambió en Python 3.0 : solo 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.