python - multiple - ¿Por qué los métodos de la superclase__init__ no se invocan automáticamente?
herencia multiple python (9)
¿Por qué los diseñadores de Python decidieron que los __init__()
las subclases __init__()
no llaman automáticamente a los __init__()
de sus superclases, como en otros lenguajes? ¿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''
"Explícito es mejor que implícito". Es el mismo razonamiento que indica que debemos escribir explícitamente ''yo''.
Creo que al final es un beneficio. ¿Puedes recitar todas las reglas que tiene Java con respecto a llamar a los constructores de las superclases?
A menudo, la subclase tiene parámetros adicionales que no se pueden pasar a la superclase.
Creo que la consideración más importante aquí es que con una llamada automática a super.__init__()
, se proscribe, por diseño, cuando se llama a ese método de inicialización y con qué argumentos. evitar el llamarlo automáticamente, y requerir que el programador haga esa llamada explícitamente, implica mucha flexibilidad.
después de todo, el hecho de que la clase B se derive de la clase A no significa que A.__init__()
pueda o deba invocarse 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 algún cálculo con esos datos, llamar a A.__init__()
con argumentos apropiados para ese método, y luego hacer algún postprocesamiento. este tipo de flexibilidad sería difícil de alcanzar si A.__init__()
fuera invocado implícitamente desde B.__init__()
, antes de que B.__init__()
ejecute o inmediatamente después.
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 se llamara automáticamente a los constructores, necesitaría otra página de al menos la misma longitud explicando el orden en que sucedió. Eso sería un infierno ...
Estoy algo avergonzado cuando las personas repiten el "Zen de Python" como si fuera una justificación para algo. Es una filosofía de diseño; las decisiones de diseño particulares siempre se pueden explicar en términos más específicos, y deben serlo, o bien el "Zen de Python" se convierte en una excusa para hacer cualquier cosa.
La razón es simple: no necesariamente construyes una clase derivada de una manera similar a cómo construyes 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 a __init__
.
Java y C ++ requieren que se llame a un constructor de clase base debido a la disposición de la memoria.
Si tiene una clase BaseClass
con un campo de BaseClass
y crea una nueva clase SubClass
que agrega un miembro field2
, entonces una instancia de SubClass
contiene espacio para field2
y field2
. Necesita un constructor de BaseClass
para completar el BaseClass
, a menos que requiera que todas las clases BaseClass
repitan la inicialización de BaseClass
en sus propios constructores. Y si field1
es privado, las clases field1
no pueden inicializar 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 ; solo son 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. Podría simplemente agregar los atributos en sí mismo si quisiera. No hay espacio reservado para un número dado de campos para cada clase en la jerarquía, y no hay diferencia entre un atributo agregado por código de un método BaseClass
y un atributo agregado por código de un método SubClass
.
Si, como es común, SubClass
realidad quiere tener todas las invariantes de BaseClass
configuradas antes de que haga su propia personalización, entonces sí, puede llamar a BaseClass.__init__()
(o usar super
, pero eso es complicado y tiene sus propios problemas a veces). Pero no es necesario. Y puedes hacerlo antes, después o con diferentes argumentos. Demonios, si quisieras, podrías llamar a la 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. Inicializas objetos escribiendo un método __init__
que establece los atributos en __init__
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 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 de objetos para establecer los valores iniciales de los atributos, y lo hace. Si quieres que haga algo más, explícalo en tu código.
La distinción crucial entre __init__
de Python y esos otros constructores de idiomas es que __init__
no es un constructor: es un inicializador (el constructor real (si hay alguno, pero, ve más tarde ;-) es __new__
y funciona de manera completamente diferente otra vez). Aunque construir todas las superclases (y, sin duda, hacerlo "antes" continúas construyendo hacia abajo) es obviamente parte de decir que estás construyendo la instancia de una subclase, que claramente no es el caso para inicializar , ya que hay muchos casos de uso en los que La inicialización de las superclases debe omitirse, modificarse, controlarse, suceder, si es que ocurre, "en el medio" de la inicialización de la subclase, y así sucesivamente.
Básicamente, la delegación de superclase 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 una delegación automática de superclase para ningún otro método cualquiera ... solo para el constructor (y si corresponde, destructor), que, como mencioné, no es lo que Python''s __init__
es. (El comportamiento de __new__
también es bastante peculiar, aunque realmente no está directamente relacionado con tu pregunta, ya que __new__
es un constructor tan peculiar que no necesariamente necesita construir nada - podría perfectamente devolver una instancia existente, o incluso una no -instancia ... claramente Python le ofrece mucho más control de la mecánica que los "otros idiomas" que tiene en mente, que también incluye no tener delegación automática en __new__
! -).
Para evitar confusiones, es útil saber que puede invocar el método base_class init () si child_class no tiene una clase init ().
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 puede encontrarlo en la clase de los niños. Necesita invocar directamente el constructor de la clase padre si ya tiene un método init () 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.
Tal vez __init__
es el método que la subclase debe anular. A veces, las subclases necesitan que la función del padre se ejecute antes de agregar el código específico de la clase, y otras veces necesitan configurar variables de instancia antes de llamar a la función del padre. Dado que no hay manera de que Python pueda saber cuándo sería más apropiado llamar a esas funciones, no debería adivinar.
Si esos no te __init__
, considera que __init__
es solo otra función. Si la función en cuestión fuera un dostuff
, ¿querría que Python llame automáticamente a la función correspondiente en la clase padre?