Por que os designers do Python decidiram que os __init__()
métodos das subclasses não chamam automaticamente os __init__()
métodos de suas superclasses, como em outras línguas? O idioma pitonico e recomendado é realmente o seguinte?
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
fonte
fonte
__init__
método e até mesmo procurar automaticamente subclasses e decorá-las.Respostas:
A distinção crucial entre o Python
__init__
e os construtores de outras linguagens é que não__init__
é um construtor: é um inicializador (o construtor real (se houver, mas veja mais adiante ;-) é e funciona completamente diferente de novo). Embora a construção de todas as superclasses (e, sem dúvida, fazê-lo "antes" você continue construindo para baixo) é obviamente parte de dizer que você está construindo a instância de uma subclasse, isso claramente não é o caso para inicializar__new__
, já que existem muitos casos de uso em que a inicialização das superclasses precisa ser ignorada, alterada, controlada - ocorrendo, se é que existe, "no meio" da inicialização da subclasse e assim por diante.Basicamente, a delegação de superclasse do inicializador não é automática no Python pelas mesmas razões que essa delegação também não é automática para outros métodos - e observe que esses "outros idiomas" não fazem delegação de superclasse automática para nenhum método. outro método também ... apenas para o construtor (e, se aplicável, o destruidor), que, como mencionei, não é o que é o Python
__init__
. (O comportamento de__new__
também é bastante peculiar, embora realmente não esteja diretamente relacionado à sua pergunta, pois__new__
é um construtor tão peculiar que na verdade não precisa necessariamente construir nada - poderia perfeitamente retornar uma instância existente, ou mesmo uma não instância. ... claramente o Python oferece muitomais controle da mecânica do que as "outras línguas" que você tem em mente, o que também inclui não ter delegação automática em__new__
si! -).fonte
__init__
não é um construtor ... o construtor real ... é__new__
". Como você observa,__new__
não se comporta como um construtor de outras línguas.__init__
é de fato muito semelhante (é chamado durante a criação de um novo objeto, após a alocação desse objeto, para definir variáveis de membro no novo objeto) e quase sempre é o lugar para implementar a funcionalidade que em outros idiomas você colocaria um construtor. Então, basta chamá-lo de construtor!__new__
também não invoca automaticamente a superclasse__new__
. Portanto, sua afirmação de que a principal distinção é que a construção envolve necessariamente a construção de superclasses, enquanto a inicialização não é inconsistente com a afirmação de que__new__
é o construtor.__init__
em docs.python.org/reference/datamodel.html#basic-customization : "Como uma restrição especial para construtores , nenhum valor pode ser retornado; isso fará com que um TypeError seja gerado em tempo de execução "(ênfase minha). Então, é oficial,__init__
é um construtor.__init__
é chamado de construtor. Esse construtor é uma função de inicialização chamada após o objeto ter sido completamente construído e inicializado em um estado padrão, incluindo seu tipo de tempo de execução final. Não é equivalente aos construtores C ++, chamados em um objeto alocado, estaticamente digitado, com um estado indefinido. Também são diferentes de__new__
, então realmente temos pelo menos quatro tipos diferentes de funções de alocação / construção / inicialização. Os idiomas usam terminologia mista, e a parte importante é o comportamento, e não a terminologia.Fico um pouco envergonhado quando as pessoas papagaiam o "Zen of Python", como se isso fosse uma justificativa para qualquer coisa. É uma filosofia de design; decisões de design específicas sempre podem ser explicadas em termos mais específicos - e devem ser, ou então o "Zen of Python" se torna uma desculpa para fazer qualquer coisa.
O motivo é simples: você não constrói necessariamente uma classe derivada de maneira semelhante à maneira como constrói a classe base. Você pode ter mais parâmetros, menos, eles podem estar em uma ordem diferente ou não estar relacionados.
Isso se aplica a todos os métodos derivados, não apenas
__init__
.fonte
Java e C ++ exigem que um construtor de classe base seja chamado devido ao layout da memória.
Se você tem uma classe
BaseClass
com um membrofield1
e cria uma nova classeSubClass
que adiciona um membrofield2
, uma instância deSubClass
contém espaço parafield1
efield2
. Você precisa de um construtor deBaseClass
preenchimentofield1
, a menos que exija que todas as classes herdadas repitamBaseClass
a inicialização de seus próprios construtores. E sefield1
for privado, as classes herdadas não poderão inicializarfield1
.Python não é Java ou C ++. Todas as instâncias de todas as classes definidas pelo usuário têm a mesma 'forma'. Eles são basicamente apenas dicionários nos quais os atributos podem ser inseridos. Antes de qualquer inicialização, todas as instâncias de todas as classes definidas pelo usuário são quase exatamente iguais ; são apenas lugares para armazenar atributos que ainda não estão sendo armazenados.
Portanto, faz sentido que uma subclasse Python não chame seu construtor de classe base. Apenas poderia adicionar os próprios atributos, se quisesse. Não há espaço reservado para um determinado número de campos para cada classe na hierarquia e não há diferença entre um atributo adicionado pelo código de um
BaseClass
método e um atributo adicionado pelo código de umSubClass
método.Se, como é comum,
SubClass
realmente quer ter todosBaseClass
os invariantes configurados antes de fazer sua própria personalização, sim, você pode simplesmente ligarBaseClass.__init__()
(ou usarsuper
, mas isso é complicado e, às vezes, tem seus próprios problemas). Mas você não precisa. E você pode fazer isso antes, depois ou com argumentos diferentes. Inferno, se você quisesse, poderia chamar oBaseClass.__init__
de outro método inteiramente do que__init__
; talvez você tenha alguma coisa bizarra de inicialização lenta.O Python consegue essa flexibilidade, mantendo as coisas simples. Você inicializa objetos escrevendo um
__init__
método que define atributosself
. É isso aí. Ele se comporta exatamente como um método, porque é exatamente um método. Não há outras regras estranhas e não intuitivas sobre o que deve ser feito primeiro, ou o que acontecerá automaticamente se você não fizer outras coisas. O único objetivo que ele precisa servir é ser um gancho para executar durante a inicialização do objeto para definir valores de atributos iniciais, e é exatamente isso. Se você quiser fazer outra coisa, escreva explicitamente isso no seu código.fonte
"Explícito é melhor que implícito." É o mesmo raciocínio que indica que devemos escrever explicitamente 'self'.
Acho que, no final, é um benefício - você pode recitar todas as regras que Java tem em relação à chamada de construtores de superclasses?
fonte
Frequentemente, a subclasse possui parâmetros extras que não podem ser passados para a superclasse.
fonte
No momento, temos uma página bastante longa descrevendo a ordem de resolução do método em caso de herança múltipla: http://www.python.org/download/releases/2.3/mro/
Se os construtores fossem chamados automaticamente, você precisaria de outra página de pelo menos o mesmo comprimento, explicando a ordem do ocorrido. Isso seria um inferno ...
fonte
Para evitar confusão, é útil saber que você pode chamar o
__init__()
método base_class se o child_class não tiver uma__init__()
classe.Exemplo:
De fato, o MRO em python procurará
__init__()
na classe pai quando não puder encontrá-lo na classe filhos. Você precisa chamar o construtor da classe pai diretamente se você já possui um__init__()
método na classe filho.Por exemplo, o código a seguir retornará um erro: pai da classe: def init (self, a = 1, b = 0): self.a = a self.b = b
fonte
Talvez
__init__
seja o método que a subclasse precisa substituir. Às vezes, as subclasses precisam da função do pai para executar antes de adicionar código específico da classe, e outras vezes precisam configurar variáveis de instância antes de chamar a função do pai. Como o Python não tem como saber quando seria mais apropriado chamar essas funções, isso não deve ser adivinhado.Se esses não o influenciarem, considere que
__init__
é apenas mais uma função. Se a função em questão fossedostuff
alternativa, você ainda desejaria que o Python chamasse automaticamente a função correspondente na classe pai?fonte
Eu acredito que a consideração muito importante aqui é que, com uma chamada automática para
super.__init__()
, você proíbe, por design, quando esse método de inicialização é chamado e com quais argumentos. evitar a chamada automática e exigir que o programador faça explicitamente essa chamada exige muita flexibilidade.afinal, apenas porque a classe B é derivada da classe A não significa que
A.__init__()
pode ou deve ser chamado com os mesmos argumentos queB.__init__()
. Tornar a chamada explícita significa que um programador pode ter, por exemplo, definirB.__init__()
parâmetros completamente diferentes, fazer alguns cálculos com esses dados, chamarA.__init__()
argumentos conforme apropriado para esse método e depois executar algum pós-processamento. esse tipo de flexibilidade seria difícil de obter seA.__init__()
fosse chamadoB.__init__()
implicitamente, antes daB.__init__()
execução ou logo após.fonte