Pregunta Comprender Python super () con __init __ () métodos [duplicar]


Esta pregunta ya tiene una respuesta aquí:

Estoy tratando de entender el uso de super(). Por lo que parece, se pueden crear ambas clases para niños, muy bien.

Tengo curiosidad por saber la diferencia real entre las siguientes 2 clases de 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()

1979
2018-02-23 00:30


origen


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 todo tipo de cosas divertidas puede pasar. Ver el documentos estándar en super si aún no lo has hecho

Tenga en cuenta que la sintaxis cambió en Python 3.0: solo puedes decir super().__init__() en lugar de super(ChildB, self).__init__() qué IMO es un poco más agradable.


1423
2018-02-23 00:37



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 correcta en el Orden de resolución de método (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ás limitado en tu capacidad de usar herencia múltiple:

        Base.__init__(self) # Avoid this.

Explico más abajo.

"¿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 obtienes una capa de indirección en el __init__ con super, que usa la clase actual para determinar la siguiente clase __init__ para buscar en el MRO.

Ilustraré esta diferencia en una respuesta en el pregunta canónica, ¿Cómo usar 'super' en Python?, lo que demuestra inyección de dependencia y herencia múltiple cooperativa.

Si Python no tenía super

Aquí hay un código que en realidad es casi equivalente a super (cómo se implementa en C, menos cierto comportamiento de comprobación y de respaldo, y traducido 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 llamar al siguiente método apropiado en el orden de resolución de métodos.

¿Cómo se hace esto en Python 3 sin que se indique explícitamente qué clase e instancia del método se invocó?

Obtiene el marco de la pila de llamadas y encuentra la clase (almacenada implícitamente como una variable libre local, __class__, haciendo que la función de llamada sea un cierre sobre la clase) y el primer argumento para esa función, que debería ser la instancia o clase que le informa qué orden de resolución de método (MRO) usar.

Como requiere ese primer argumento para el MRO, utilizando super con métodos estáticos es imposible.

Criticas 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 todo tipo de cosas divertidas pueden suceder. Consulte los documentos estándar en super si aún no lo ha hecho.

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

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 creemos una dependencia a la que nos quiera llamar después del Niño:

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

Ahora recuerda, 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 usos 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, lo que sugiere otra respuesta, ya que definitivamente obtendrá errores cuando subclase ChildB:

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

(Esa respuesta no es inteligente ni particularmente interesante, pero a pesar de la crítica directa en los comentarios y más de 17 votos a favor, el que responde persistió en sugerirla hasta que un amable editor solucionó su problema).

Explicación: Esa respuesta sugería llamar súper así:

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

Esto es completamente incorrecto. super nos permite buscar el siguiente padre en el MRO (vea la primera sección de esta respuesta) para clases de niños. Si tu dices super estamos en el método de la instancia hija, luego buscará el siguiente método en línea (probablemente este) dando como resultado la recursión, probablemente causando una falla lógica (en el ejemplo del que responde, sí) o una RuntimeError cuando se excede la profundidad de 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'

407
2017-11-25 19:00



Se ha observado que en Python 3.0+ puedes usar

super().__init__() 

para hacer su llamada, que es conciso y no requiere que haga referencia explícitamente al padre O nombres de clase, lo que puede ser útil. Solo quiero agregar que para Python 2.7 o posterior, es posible obtener este comportamiento insensible al nombre escribiendo self.__class__ en lugar del nombre de la clase, es decir

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

SIN EMBARGO, esto rompe llamadas a super para cualquier clase que herede de su clase, donde self.__class__ podría devolver una clase infantil. 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 de Rectangle es lo suficientemente bueno, pero por alguna razón quiero implementar un cuadrado para poder reimplementar otro método.

Cuando creo un Square utilizando mSquare = Square('a', 10,10), Python llama al constructor para Rectangle porque no he dado Square su propio constructor Sin embargo, en el constructor de Rectangle, la llamada super(self.__class__,self) va a devolver la superclase de mSquare, por lo que llama al constructor para Rectangle de nuevo. Así es como ocurre el ciclo infinito, como fue mencionado por @S_C. En este caso, cuando corro super(...).__init__() Estoy llamando al constructor para Rectangle pero como no le doy argumentos, obtendré un error.


221
2017-10-08 20:08



Super no tiene efectos secundarios

Base = ChildB

Base()

funciona como se esperaba

Base = ChildA

Base()

entra en la recursión infinita.


75
2017-11-27 23:26



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

Personalmente, en cuanto al código de Python 2.7, voy a seguir usando BaseClassName.__init__(self, args) hasta que realmente tenga la ventaja de usar super().


68
2018-05-25 17:52



No hay, realmente. super() mira la siguiente clase en el MRO (orden de resolución de método, accedido 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. Entonces, realmente estás haciendo exactamente lo mismo, pero de una manera más agradable con super() (Particularmente si ingresa en herencia múltiple más adelante).


45
2018-02-23 00:34



La principal diferencia es que ChildA.__init__ llamará incondicionalmente Base.__init__ mientras ChildB.__init__ llamará __init__ en cualquiera sea la clase que sea ChildB antepasado en selflínea de antepasados (que puede diferir de lo que espera).

Si agrega un 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. Ahora super(ChildB, self) señalará a Mixin Si self es un ChildC ejemplo.

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

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

los Super Súper considerado y video de acompañamiento de pycon 2015 explica esto bastante bien


22
2017-09-21 06:41