¿Por qué no se invocan automáticamente los métodos de superclase __init__?

155

¿Por qué los diseñadores de Python decidieron que los __init__()métodos de las subclases no llaman automáticamente a los __init__()métodos de sus superclases, como en otros idiomas? ¿Es el idioma Pythonic y recomendado realmente como el siguiente?

class Superclass(object):
    def __init__(self):
        print 'Do something'

class Subclass(Superclass):
    def __init__(self):
        super(Subclass, self).__init__()
        print 'Do something else'
jrdioko
fuente
2
Puede escribir un decorador para heredar el __init__método, e incluso quizás buscar automáticamente subclases y decorarlas.
osa
2
@osa, suena una muy buena idea. ¿Te gustaría describir un poco más en la parte del decorador?
Diansheng
1
@osa sí, describa más!
Charlie Parker

Respuestas:

163

La distinción crucial entre __init__los constructores de Python y esos otros lenguajes es que no__init__ es un constructor: es un inicializador (el constructor real (si existe, pero, ver más adelante ;-) es y funciona de manera completamente diferente nuevamente). Si bien construir todas las superclases (y, sin duda, hacerlo "antes" de continuar construyendo hacia abajo) obviamente es parte de decir que está construyendo una instancia de subclase, que claramente no es el caso para inicializar__new__, dado que hay muchos casos de uso en los que la inicialización de las superclases debe omitirse, alterarse, controlarse, sucediendo, en todo caso, "en el medio" de la inicialización de la subclase, y así sucesivamente.

Básicamente, la delegación de superclases del inicializador no es automática en Python por exactamente las mismas razones por las que dicha delegación tampoco es automática para ningún otro método, y tenga en cuenta que esos "otros idiomas" no hacen la delegación automática de superclases para ningún otro método tampoco ... solo para el constructor (y si corresponde, destructor), que, como mencioné, no es lo que es Python __init__. (El comportamiento __new__también es bastante peculiar, aunque en realidad no está directamente relacionado con su pregunta, ya que __new__es un constructor tan peculiar que en realidad no necesariamente tiene que construir nada; podría perfectamente devolver una instancia existente, o incluso una no instancia ... claramente Python te ofrece muchotiene más control de la mecánica que los "otros idiomas" que tiene en mente, ¡lo que también incluye no tener delegación automática en __new__sí misma! -).

Alex Martelli
fuente
77
Voté porque, para mí, el beneficio real es la capacidad que mencionas para llamar a la init de la superclase _ init () _ en cualquier punto de la inicialización de la subclase (o no).
poco
53
-1 para " __init__no es un constructor ... el constructor real ... es __new__". Como usted mismo nota, __new__no se comporta como un constructor de otros idiomas. __init__de hecho es muy similar (se llama durante la creación de un nuevo objeto, después de que se asigna ese objeto, para establecer variables miembro en el nuevo objeto), y casi siempre es el lugar para implementar funcionalidades que en otros idiomas se pondrían Un constructor. ¡Así que llámalo constructor!
Ben
8
También creo que esta es una afirmación bastante absurda: " construir todas las superclases ... obviamente es parte de decir que estás construyendo la instancia de una subclase, que claramente no es el caso para la inicialización ". No hay nada en las palabras construcción / inicialización que haga esto "obvio" para nadie. Y __new__tampoco invoca automáticamente la superclase __new__. Entonces, su afirmación de que la distinción clave es que la construcción implica necesariamente la construcción de superclases, mientras que la inicialización no es inconsistente con su afirmación de que __new__es el constructor.
Ben
36
De hecho, de los documentos de Python __init__en docs.python.org/reference/datamodel.html#basic-customization : "Como restricción especial en los constructores , no se puede devolver ningún valor; al hacerlo, se generará un TypeError en tiempo de ejecución "(énfasis mío). Entonces, es oficial, __init__es un constructor.
Ben
3
En terminología Python / Java, __init__se llama constructor. Este constructor es una función de inicialización llamada después de que el objeto ha sido completamente construido e inicializado a un estado predeterminado, incluido su tipo de tiempo de ejecución final. No es equivalente a los constructores de C ++, que se invocan en un objeto asignado de tipo estático con un estado indefinido. Estos también son diferentes __new__, por lo que realmente tenemos al menos cuatro tipos diferentes de funciones de asignación / construcción / inicialización. Los idiomas usan terminología mixta, y la parte importante es el comportamiento y no la terminología.
Elazar
36

Me da un poco de vergüenza cuando la gente repite el "Zen de Python", como si fuera una justificación para algo. Es una filosofía de diseño; las decisiones particulares de diseño siempre pueden explicarse en términos más específicos, y deben serlo, o el "Zen de Python" se convierte en una excusa para hacer cualquier cosa.

La razón es simple: no necesariamente construye una clase derivada de una manera similar a cómo construye la clase base. Puede tener más parámetros, menos, pueden estar en un orden diferente o no estar relacionados en absoluto.

class myFile(object):
    def __init__(self, filename, mode):
        self.f = open(filename, mode)
class readFile(myFile):
    def __init__(self, filename):
        super(readFile, self).__init__(filename, "r")
class tempFile(myFile):
    def __init__(self, mode):
        super(tempFile, self).__init__("/tmp/file", mode)
class wordsFile(myFile):
    def __init__(self, language):
        super(wordsFile, self).__init__("/usr/share/dict/%s" % language, "r")

Esto se aplica a todos los métodos derivados, no solo __init__.

Glenn Maynard
fuente
66
¿Este ejemplo ofrece algo especial? los lenguajes estáticos también pueden hacer esto
jean
18

Java y C ++ requieren que se llame a un constructor de clase base debido al diseño de la memoria.

Si tiene una clase BaseClasscon un miembro field1y crea una nueva clase SubClassque agrega un miembro field2, entonces una instancia de SubClasscontiene espacio para field1y field2. Necesita un constructor de BaseClasspara completar field1, a menos que requiera que todas las clases heredadas repitan BaseClassla inicialización en sus propios constructores. Y si field1es privado, entonces las clases heredadas no pueden inicializarse field1.

Python no es Java o C ++. Todas las instancias de todas las clases definidas por el usuario tienen la misma 'forma'. Básicamente son solo diccionarios en los que se pueden insertar atributos. Antes de realizar cualquier inicialización, todas las instancias de todas las clases definidas por el usuario son casi exactamente iguales ; son solo lugares para almacenar atributos que aún no se almacenan.

Por lo tanto, tiene mucho sentido que una subclase de Python no llame a su constructor de clase base. Simplemente podría agregar los atributos en sí si quisiera. No hay espacio reservado para un número determinado de campos para cada clase en la jerarquía, y no hay diferencia entre un atributo agregado por código de un BaseClassmétodo y un atributo agregado por código de un SubClassmétodo.

Si, como es común, en SubClassrealidad quiere tener todos BaseClasslos invariantes configurados antes de continuar con su propia personalización, entonces sí, simplemente puede llamar BaseClass.__init__()(o usar super, pero eso es complicado y a veces tiene sus propios problemas). Pero no tienes que hacerlo. Y puede hacerlo antes, o después, o con diferentes argumentos. Demonios, si quisieras, puedes llamar BaseClass.__init__desde otro método completamente distinto de __init__; tal vez tengas algo extraño de inicialización perezosa.

Python logra esta flexibilidad manteniendo las cosas simples. Inicializa objetos escribiendo un __init__método que establece atributos en self. Eso es. Se comporta exactamente como un método, porque es exactamente un método. No hay otras reglas extrañas y poco intuitivas sobre las cosas que deben hacerse primero, o cosas que sucederán automáticamente si no haces otras cosas. El único propósito que debe cumplir es ser un gancho para ejecutar durante la inicialización del objeto para establecer los valores de los atributos iniciales, y lo hace. Si quiere que haga otra cosa, explícitamente escriba eso en su código.

Ben
fuente
2
En cuanto a C ++, no tiene nada que ver con el "diseño de memoria". El mismo modelo de inicialización que ofrece Python podría haberse implementado en C ++. La única razón por la cual la construcción / destrucción son como están en C ++ es por la decisión de diseño de proporcionar instalaciones confiables y de buen comportamiento para la gestión de recursos (RAII), que también podrían generarse automáticamente (lo que significa menos código y errores humanos) por compiladores ya que las reglas para ellos (su orden de llamada) están rigurosamente definidos. No estoy seguro, pero Java probablemente siguió este enfoque para ser otro lenguaje similar a POLA C.
Alexander Shukaev
10

"Explícito es mejor que implícito". Es el mismo razonamiento que indica que debemos escribir explícitamente 'self'.

Creo que al final es un beneficio: ¿puedes recitar todas las reglas que Java tiene para llamar a los constructores de superclases?

Mike Axiak
fuente
8
Estoy de acuerdo con usted en su mayor parte, pero la regla de Java es bastante simple: se invoca el constructor sin argumentos a menos que solicite específicamente uno diferente.
Laurence Gonsalves
1
@Laurence: ¿qué sucede cuando la clase principal no define un constructor sin argumentos? ¿Qué sucede cuando el constructor sin argumentos está protegido o es privado?
Mike Axiak
8
Exactamente lo mismo que sucedería si intentaras llamarlo explícitamente.
Laurence Gonsalves
8

A menudo, la subclase tiene parámetros adicionales que no se pueden pasar a la superclase.

John La Rooy
fuente
7

En este momento, tenemos una página bastante larga que describe el orden de resolución del método en caso de herencia múltiple: http://www.python.org/download/releases/2.3/mro/

Si los constructores se llamaran automáticamente, necesitaría otra página de al menos la misma longitud que explique el orden en que sucedió. Eso sería un infierno ...

viraptor
fuente
Esta fue la respuesta correcta. Python necesitaría definir la semántica del argumento que pasa entre la subclase y la superclase. Lástima que esta respuesta no haya sido votada. ¿Quizás si mostrara el problema con algunos ejemplos?
Gary Weiss
5

Para evitar confusiones, es útil saber que puede invocar el __init__()método base_class si child_class no tiene una __init__()clase.

Ejemplo:

class parent:
  def __init__(self, a=1, b=0):
    self.a = a
    self.b = b

class child(parent):
  def me(self):
    pass

p = child(5, 4)
q = child(7)
z= child()

print p.a # prints 5
print q.b # prints 0
print z.a # prints 1

De hecho, el MRO en python buscará __init__()en la clase padre cuando no pueda encontrarlo en la clase infantil. Debe invocar el constructor de la clase principal directamente si ya tiene un __init__()método en la clase secundaria.

Por ejemplo, el siguiente código devolverá un error: class parent: def init (self, a = 1, b = 0): self.a = a self.b = b

    class child(parent):
      def __init__(self):
        pass
      def me(self):
        pass

    p = child(5, 4) # Error: constructor gets one argument 3 is provided.
    q = child(7)  # Error: constructor gets one argument 2 is provided.

    z= child()
    print z.a # Error: No attribute named as a can be found.
Keyvanrm
fuente
3

Quizás __init__es el método que la subclase necesita anular. Algunas veces las subclases necesitan que la función del padre se ejecute antes de agregar código específico de clase, y otras veces necesitan configurar variables de instancia antes de llamar a la función del padre. Como no hay forma de que Python pueda saber cuándo sería más apropiado llamar a esas funciones, no debería adivinar.

Si esos no te influyen, considera que __init__es solo otra función. Si la función en cuestión fuera dostuff, ¿querrías que Python llame automáticamente a la función correspondiente en la clase principal?

Kirk Strauser
fuente
2

Creo que la consideración más importante aquí es que con una llamada automática a super.__init__(), usted proscribe, por diseño, cuándo se llama a ese método de inicialización y con qué argumentos. evitar llamarlo automáticamente y exigirle al programador que haga esa llamada explícitamente implica mucha flexibilidad.

después de todo, solo porque la clase B se deriva de la clase A no significa que A.__init__()pueda o deba llamarse con los mismos argumentos que B.__init__(). hacer que la llamada sea explícita significa que un programador puede tener, por ejemplo, definir B.__init__()con parámetros completamente diferentes, hacer algunos cálculos con esos datos, llamar A.__init__()con argumentos según corresponda para ese método, y luego hacer algo de procesamiento posterior. Este tipo de flexibilidad sería difícil de lograr si A.__init__()se llamara B.__init__()implícitamente, ya sea antes de B.__init__()ejecutarlo o justo después.

fluir
fuente