¿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'
python
inheritance
subclass
delegation
superclass
jrdioko
fuente
fuente
__init__
método, e incluso quizás buscar automáticamente subclases y decorarlas.Respuestas:
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! -).fuente
__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!__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.__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.__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.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.
Esto se aplica a todos los métodos derivados, no solo
__init__
.fuente
Java y C ++ requieren que se llame a un constructor de clase base debido al diseño de la memoria.
Si tiene una clase
BaseClass
con un miembrofield1
y crea una nueva claseSubClass
que agrega un miembrofield2
, entonces una instancia deSubClass
contiene espacio parafield1
yfield2
. Necesita un constructor deBaseClass
para completarfield1
, a menos que requiera que todas las clases heredadas repitanBaseClass
la inicialización en sus propios constructores. Y sifield1
es privado, entonces las clases heredadas no pueden inicializarsefield1
.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
BaseClass
método y un atributo agregado por código de unSubClass
método.Si, como es común, en
SubClass
realidad quiere tener todosBaseClass
los invariantes configurados antes de continuar con su propia personalización, entonces sí, simplemente puede llamarBaseClass.__init__()
(o usarsuper
, 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 llamarBaseClass.__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 enself
. 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.fuente
"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?
fuente
A menudo, la subclase tiene parámetros adicionales que no se pueden pasar a la superclase.
fuente
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 ...
fuente
Para evitar confusiones, es útil saber que puede invocar el
__init__()
método base_class si child_class no tiene una__init__()
clase.Ejemplo:
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
fuente
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 fueradostuff
, ¿querrías que Python llame automáticamente a la función correspondiente en la clase principal?fuente
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 queB.__init__()
. hacer que la llamada sea explícita significa que un programador puede tener, por ejemplo, definirB.__init__()
con parámetros completamente diferentes, hacer algunos cálculos con esos datos, llamarA.__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 siA.__init__()
se llamaraB.__init__()
implícitamente, ya sea antes deB.__init__()
ejecutarlo o justo después.fuente