¿Debería __init __ () llamar a la clase principal __init __ ()?


132

Estoy acostumbrado a que en Objective-C tengo esta construcción:

- (void)init {
    if (self = [super init]) {
        // init class
    }
    return self;
}

¿Python también debería llamar a la implementación de la clase padre __init__?

class NewClass(SomeOtherClass):
    def __init__(self):
        SomeOtherClass.__init__(self)
        # init class

¿Es esto también verdadero / falso para __new__()y __del__()?

Editar: Hay una pregunta muy similar: Herencia y anulación __init__en Python


Has cambiado tu código significativamente. Puedo entender que el original objectfue un error tipográfico. Pero ahora ni siquiera tiene el supertítulo de su pregunta se refiere.
SilentGhost

Solo pensé que super se usa como un nombre para la clase padre. No pensé que nadie pensaría en la función. Perdón por cualquier malentendido.
Georg Schölly

Respuestas:


67

En Python, llamar a la superclase ' __init__es opcional. Si lo llama, también es opcional usar el superidentificador o nombrar explícitamente la superclase:

object.__init__(self)

En el caso de un objeto, no es estrictamente necesario llamar al súper método, ya que el súper método está vacío. Lo mismo para __del__.

Por otro lado, para __new__, de hecho, debería llamar al supermétodo y usar su retorno como el objeto recién creado, a menos que explícitamente desee devolver algo diferente.


Entonces, ¿no hay una convención para simplemente llamar a la implementación de super?
Georg Schölly

55
En las clases de estilo antiguo, solo se podía llamar al superiniciado si la superclase realmente tenía un init definido (que a menudo no tiene). Por lo tanto, las personas generalmente piensan en llamar al súper método, en lugar de hacerlo por principio.
Martin v. Löwis

1
Si la sintaxis en python fuera tan simple como [super init], sería más común. Solo un pensamiento especulativo; la súper construcción en Python 2.x es un poco incómoda para mí.
u0b34a0f6ae

Aquí parece ser un ejemplo interesante (y posiblemente contradictorio): bytes.com/topic/python/answers/… init
mlvljr

"opcional" en el sentido de que no tiene que llamarlo, pero si no lo llama, no se llamará automáticamente.
McKay

140

Si necesita hacer algo de super __init__además de lo que se está haciendo en la clase actual, __init__,debe llamarlo usted mismo, ya que eso no sucederá automáticamente. Pero si no necesita nada de Super, __init__,no necesita llamarlo. Ejemplo:

>>> class C(object):
        def __init__(self):
            self.b = 1


>>> class D(C):
        def __init__(self):
            super().__init__() # in Python 2 use super(D, self).__init__()
            self.a = 1


>>> class E(C):
        def __init__(self):
            self.a = 1


>>> d = D()
>>> d.a
1
>>> d.b  # This works because of the call to super's init
1
>>> e = E()
>>> e.a
1
>>> e.b  # This is going to fail since nothing in E initializes b...
Traceback (most recent call last):
  File "<pyshell#70>", line 1, in <module>
    e.b  # This is going to fail since nothing in E initializes b...
AttributeError: 'E' object has no attribute 'b'

__del__es de la misma manera, (pero tenga cuidado de no confiar en la __del__finalización; considere hacerlo a través de la declaración with en su lugar).

Raramente uso __new__. hago toda la inicialización en__init__.


3
La definición de clase D (C) debe corregirse asísuper(D,self).__init__()
eyquem el

11
super () .__ init __ () solo funciona en Python 3. En Python 2 necesitas super (D, self) .__ init __ ()
Jacinda

"Si necesita algo de super's init ..." - Esta es una afirmación muy problemática porque no se trata de si usted / la subclase necesita "algo", sino si la clase base necesita algo para ser válida instancia de clase base y funciona correctamente. Como implementador de la clase derivada, las partes internas de la clase base son cosas que no puede / no debe saber, e incluso si lo hace porque escribió ambas o las partes internas están documentadas, el diseño de la base puede cambiar en el futuro y romperse debido a una mala escritura clase derivada. Por lo tanto, asegúrese siempre de que la clase base se inicialice por completo.
Nick

105

En la respuesta de Anon:
"Si necesita hacer algo de super __init__además de lo que se está haciendo en la clase actual __init__, debe llamarlo usted mismo, ya que eso no sucederá automáticamente"

Es increíble: está redactando exactamente lo contrario del principio de herencia.


No es que "algo de super __init__ (...) no suceda automáticamente" , es que sucedería automáticamente, pero no sucede porque la clase base ' __init__es anulada por la definición de la clase derivada__init__

Entonces, ¿POR QUÉ definir una "clase derivada" __init__, ya que anula lo que se pretende cuando alguien recurre a la herencia?

Es porque uno necesita definir algo que NO se hace en la clase base ' __init__, y la única posibilidad de obtener eso es poner su ejecución en una __init__función de clase derivada' .
En otras palabras, uno necesita algo en la clase base ' __init__además de lo que se haría automáticamente en la clase base' __init__si este último no fuera anulado.
NO al contrario.


Entonces, el problema es que las instrucciones deseadas presentes en la clase base ' __init__ya no se activan en el momento de la instanciación. Para compensar esta inactivación, se requiere algo especial: llamar explícitamente a la clase base ' __init__, para MANTENER , NO AGREGAR, la inicialización realizada por la clase base' __init__. Eso es exactamente lo que se dice en el documento oficial:

De hecho, un método de anulación en una clase derivada puede querer extender en lugar de simplemente reemplazar el método de la clase base del mismo nombre. Hay una manera simple de llamar directamente al método de la clase base: simplemente llame a BaseClassName.methodname (self, argumentos).
http://docs.python.org/tutorial/classes.html#inheritance

Esa es toda la historia:

  • cuando el objetivo es MANTENER la inicialización realizada por la clase base, que es pura herencia, no se necesita nada especial, solo se debe evitar definir una __init__función en la clase derivada

  • cuando el objetivo es REEMPLAZAR la inicialización realizada por la clase base, __init__debe definirse en la clase derivada

  • cuando el objetivo es AGREGAR procesos a la inicialización realizada por la clase base, se __init__ debe definir una clase derivada que comprenda una llamada explícita a la clase base__init__


Lo que siento asombroso en la publicación de Anon no es solo que él exprese lo contrario de la teoría de la herencia, sino que ha habido 5 tipos pasando por alto sin votar, y además no ha habido nadie que reaccione en 2 años en un hilo cuyo tema interesante debe leerse con relativa frecuencia.


1
Estaba seguro de que esta publicación iba a ser votada. Me temo que no tendré muchas explicaciones sobre los motivos. Es más fácil votar negativamente que analizar un texto que parece incomprensible. Pasé mucho tiempo tratando de entender la publicación de Anon antes de darme cuenta finalmente de que estaba escrita con engaño y no tenía mucha autoridad. Tal vez se pueda interpretar como aproximadamente adecuado para alguien que conoce la herencia; pero me resulta confusioning cuando se lee por alguien que tenga nociones inestables sobre la herencia, un tema no es tan claro como el agua rock en general
EYQUEM

2
"Estaba seguro de que esta publicación iba a ser votada ..." El problema principal es que llegas un poco tarde a la fiesta, y la mayoría de la gente puede no leer más allá de las primeras respuestas. Gran explicación por cierto +1
Gerrat

Gran respuesta es esta.
Trilarion

3
Te perdiste las palabras significativas "además" en la oración que citaste de Aaron. La afirmación de Aaron es completamente correcta y coincide con lo que terminas diciendo.
GreenAsJade

1
Esta es la primera explicación que hizo que la elección del diseño de Python tuviera sentido.
Joseph Garvin el

20

Editar : (después del cambio de código)
No hay forma de que le digamos si necesita o no llamar a sus padres __init__(o cualquier otra función). La herencia obviamente funcionaría sin tal llamado. Todo depende de la lógica de su código: por ejemplo, si todo __init__se realiza en la clase principal, puede omitir la clase secundaria por __init__completo.

considere el siguiente ejemplo:

>>> class A:
    def __init__(self, val):
        self.a = val


>>> class B(A):
    pass

>>> class C(A):
    def __init__(self, val):
        A.__init__(self, val)
        self.a += val


>>> A(4).a
4
>>> B(5).a
5
>>> C(6).a
12

Eliminé la súper llamada de mi ejemplo, si quería saber si uno debería llamar a la implementación de init de la clase padre o no.
Georg Schölly

es posible que desee editar el título entonces. pero mi respuesta sigue en pie.
SilentGhost

5

No hay una regla dura y rápida. La documentación de una clase debe indicar si las subclases deben llamar al método de superclase. A veces, desea reemplazar por completo el comportamiento de la superclase y otras veces aumentarlo, es decir, llamar a su propio código antes y / o después de una llamada de la superclase.

Actualización: la misma lógica básica se aplica a cualquier llamada a método. Los constructores a veces necesitan una consideración especial (ya que a menudo establecen un estado que determina el comportamiento) y destructores porque son constructores paralelos (por ejemplo, en la asignación de recursos, por ejemplo, conexiones de bases de datos). Pero lo mismo podría aplicarse, por ejemplo, al render()método de un widget.

Actualización adicional: ¿Qué es el OPP? ¿Te refieres a OOP? No, una subclase a menudo necesita saber algo sobre el diseño de la superclase. No los detalles de implementación internos, sino el contrato básico que la superclase tiene con sus clientes (usando clases). Esto no viola los principios de OOP de ninguna manera. Es por eso que protectedes un concepto válido en OOP en general (aunque no, por supuesto, en Python).


Usted dijo que a veces uno querría llamar a su propio código antes de la llamada de la superclase. Para hacer esto, uno necesita conocimiento de la implementación de la clase padre, lo que violaría el OPP.
Georg Schölly, el

4

OMI, deberías llamarlo. Si su superclase es object, no debería, pero en otros casos creo que es excepcional no llamarla. Como ya respondieron otros, es muy conveniente si su clase ni siquiera tiene que anularse __init__, por ejemplo, cuando no tiene un estado interno (adicional) para inicializar.


2

Sí, siempre debe llamar a la clase base __init__explícitamente como una buena práctica de codificación. Olvidar hacer esto puede causar problemas sutiles o errores de tiempo de ejecución. Esto es cierto incluso si __init__no toma ningún parámetro. Esto es diferente a otros lenguajes donde el compilador llamaría implícitamente al constructor de la clase base por usted. ¡Python no hace eso!

La razón principal para llamar siempre a la clase base _init__es que la clase base generalmente puede crear variables miembro e inicializarlas a los valores predeterminados. Entonces, si no llama a la clase base init, ninguno de ese código se ejecutará y terminará con la clase base que no tiene variables miembro.

Ejemplo :

class Base:
  def __init__(self):
    print('base init')

class Derived1(Base):
  def __init__(self):
    print('derived1 init')

class Derived2(Base):
  def __init__(self):
    super(Derived2, self).__init__()
    print('derived2 init')

print('Creating Derived1...')
d1 = Derived1()
print('Creating Derived2...')
d2 = Derived2()

Esto imprime ..

Creating Derived1...
derived1 init
Creating Derived2...
base init
derived2 init

Ejecute este código .

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.