Digamos que eu tenha um cenário de herança múltipla:
class A(object):
# code for A here
class B(object):
# code for B here
class C(A, B):
def __init__(self):
# What's the right code to write here to ensure
# A.__init__ and B.__init__ get called?
Há duas abordagens típicas para escrever C
's __init__
:
- (estilo antigo)
ParentClass.__init__(self)
- (estilo mais recente)
super(DerivedClass, self).__init__()
No entanto, em ambos os casos, se as classes pai ( A
e B
) não seguirem a mesma convenção, o código não funcionará corretamente (alguns podem ser perdidos ou chamados várias vezes).
Então, qual é o caminho correto de novo? É fácil dizer "apenas ser consistente, siga um ou outro", mas se A
ou B
são de uma biblioteca parte 3, o que então? Existe uma abordagem que pode garantir que todos os construtores da classe pai sejam chamados (e na ordem correta e apenas uma vez)?
Edit: para ver o que quero dizer, se eu fizer:
class A(object):
def __init__(self):
print("Entering A")
super(A, self).__init__()
print("Leaving A")
class B(object):
def __init__(self):
print("Entering B")
super(B, self).__init__()
print("Leaving B")
class C(A, B):
def __init__(self):
print("Entering C")
A.__init__(self)
B.__init__(self)
print("Leaving C")
Então eu recebo:
Entering C
Entering A
Entering B
Leaving B
Leaving A
Entering B
Leaving B
Leaving C
Note que B
o init é chamado duas vezes. Se eu fizer:
class A(object):
def __init__(self):
print("Entering A")
print("Leaving A")
class B(object):
def __init__(self):
print("Entering B")
super(B, self).__init__()
print("Leaving B")
class C(A, B):
def __init__(self):
print("Entering C")
super(C, self).__init__()
print("Leaving C")
Então eu recebo:
Entering C
Entering A
Leaving A
Leaving C
Note que B
o init nunca é chamado. Parece que, a menos que eu conheça / controle os init das classes que herdo ( A
e B
), não posso fazer uma escolha segura para a classe que estou escrevendo ( C
).
fonte
Respostas:
Ambas as formas funcionam bem. A abordagem utilizada
super()
leva a uma maior flexibilidade para subclasses.Na abordagem de chamada direta,
C.__init__
pode chamar ambosA.__init__
eB.__init__
.Ao usar
super()
, as classes precisam ser projetadas para herança múltipla cooperativa whereC
callssuper
, que chamaA
o código da qual também chamasuper
qualB
código da chamada . Veja http://rhettinger.wordpress.com/2011/05/26/super-considered-super para obter mais detalhes sobre o que pode ser feitosuper
.[Pergunta de resposta editada posteriormente]
O artigo mencionado mostra como lidar com essa situação adicionando uma classe de wrapper em torno de
A
eB
. Há um exemplo elaborado na seção intitulada "Como incorporar uma classe não cooperativa".Pode-se desejar que a herança múltipla seja mais fácil, permitindo que você componha sem esforço as classes Car e Airplane para obter um FlyingCar, mas a realidade é que componentes projetados separadamente geralmente precisam de adaptadores ou invólucros antes de se encaixarem da maneira que gostaríamos :-)
Outro pensamento: se você está insatisfeito com a funcionalidade de composição usando herança múltipla, pode usar a composição para ter controle total sobre quais métodos são chamados em quais ocasiões.
fonte
super().__init__()
abordagem. Se eu ligarA.__init__()
eB.__init__()
diretamente, então (se A e B telefonamsuper
), recebo o init de B sendo chamado várias vezes.A resposta para sua pergunta depende de um aspecto muito importante: suas classes base são projetadas para herança múltipla?
Existem 3 cenários diferentes:
As classes base são classes independentes e não relacionadas.
Se suas classes base são entidades separadas que são capazes de funcionar de forma independente e não se conhecem, elas não foram projetadas para herança múltipla. Exemplo:
Importante: Observe que nem
Foo
nemBar
chamasuper().__init__()
! É por isso que seu código não funcionou corretamente. Devido à maneira como a herança de diamantes funciona em python, as classes cuja classe base éobject
não devem chamarsuper().__init__()
. Como você notou, fazer isso quebraria várias heranças porque você acaba chamando outra classe em__init__
vez deobject.__init__()
. ( Disclaimer: Evitarsuper().__init__()
emobject
-subclasses é a minha recomendação pessoal e não significa um acordados consenso na comunidade python Algumas pessoas preferem o uso.super
Em todas as classes, argumentando que você sempre pode escrever um adaptador se a classe não se comporta como você espera.)Isso também significa que você nunca deve escrever uma classe que herda
object
e não possui um__init__
método. Não definir um__init__
método tem o mesmo efeito que chamarsuper().__init__()
. Se sua classe herdar diretamente deobject
, adicione um construtor vazio da seguinte maneira:De qualquer forma, nessa situação, você precisará chamar cada construtor pai manualmente. Existem duas maneiras de fazer isso:
Sem
super
Com
super
Cada um desses dois métodos tem suas próprias vantagens e desvantagens. Se você usar
super
, sua classe oferecerá suporte à injeção de dependência . Por outro lado, é mais fácil cometer erros. Por exemplo, se você alterar a ordem deFoo
eBar
(comoclass FooBar(Bar, Foo)
), precisará atualizar assuper
chamadas para que correspondam. Semsuper
você não precisa se preocupar com isso, e o código é muito mais legível.Uma das classes é um mixin.
Um mixin é uma classe projetada para ser usada com herança múltipla. Isso significa que não precisamos chamar os dois construtores-pai manualmente, porque o mixin chamará automaticamente o segundo construtor para nós. Como só precisamos chamar um único construtor dessa vez, podemos fazer isso
super
para evitar codificar o nome da classe pai.Exemplo:
Os detalhes importantes aqui são:
super().__init__()
e passa por todos os argumentos que recebe.class FooBar(FooMixin, Bar)
. Se a ordem das classes base estiver incorreta, o construtor do mixin nunca será chamado.Todas as classes base são projetadas para herança cooperativa.
Classes projetadas para herança cooperativa são muito parecidas com mixins: elas passam por todos os argumentos não utilizados para a próxima classe. Como antes, basta chamar
super().__init__()
e todos os construtores-pai serão chamados em cadeia.Exemplo:
Nesse caso, a ordem das classes pai não importa. Podemos herdar
CoopBar
primeiro, e o código ainda funcionaria da mesma maneira. Mas isso só é verdade porque todos os argumentos são passados como argumentos de palavras-chave. O uso de argumentos posicionais tornaria fácil a ordem dos argumentos errados; portanto, é habitual que as classes cooperativas aceitem apenas argumentos de palavras-chave.Isso também é uma exceção à regra que mencionei anteriormente: Ambos
CoopFoo
eCoopBar
herdam deobject
, mas eles ainda chamamsuper().__init__()
. Se não o fizessem, não haveria herança cooperativa.Conclusão: a implementação correta depende das classes das quais você está herdando.
O construtor faz parte da interface pública de uma classe. Se a classe for projetada como uma combinação ou herança de cooperação, isso deve ser documentado. Se os documentos não mencionarem nada disso, é seguro assumir que a classe não foi projetada para herança múltipla cooperativa.
fonte
super().__init__(*args, **kwargs)
na mixagem e escrever primeiro. faz muito sentido.Qualquer abordagem ("novo estilo" ou "estilo antigo") funcionará se você tiver controle sobre o código fonte para
A
eB
. Caso contrário, o uso de uma classe de adaptador pode ser necessário.Código fonte acessível: uso correto de "novo estilo"
Aqui, a ordem de resolução de método (MRO) determina o seguinte:
C(A, B)
ditaA
primeiro, entãoB
. MRO éC -> A -> B -> object
.super(A, self).__init__()
continua ao longo da cadeia de MRO iniciado emC.__init__
aB.__init__
.super(B, self).__init__()
continua ao longo da cadeia de MRO iniciado emC.__init__
aobject.__init__
.Você poderia dizer que este caso foi projetado para herança múltipla .
Código fonte acessível: uso correto do "estilo antigo"
Aqui, MRO não importa, desde
A.__init__
eB.__init__
são chamados explicitamente.class C(B, A):
funcionaria tão bem.Embora este caso não seja "projetado" para herança múltipla no novo estilo como o anterior, ainda é possível a herança múltipla.
Agora, o que se
A
eB
de uma biblioteca de terceiros - ou seja, você não tem controle sobre o código-fonte paraA
eB
? A resposta curta: você deve projetar uma classe de adaptador que implemente assuper
chamadas necessárias e, em seguida, usar uma classe vazia para definir o MRO (consulte o artigo de Raymond Hettingersuper
- especialmente a seção "Como incorporar uma classe não cooperativa").Pais de terceiros:
A
não implementamsuper
;B
fazA classe é
Adapter
implementadasuper
paraC
definir o MRO, que entra em ação quandosuper(Adapter, self).__init__()
é executado.E se for o contrário?
Pais de terceiros:
A
implementossuper
;B
nãoO mesmo padrão aqui, exceto que a ordem de execução é ativada
Adapter.__init__
;super
primeiro, depois a chamada explícita. Observe que cada caso com pais de terceiros requer uma classe de adaptador exclusiva.Embora você possa lidar com os casos em que você não controla o código-fonte
A
eB
usando uma classe de adaptador, é verdade que você deve saber como os init's das classes-pai implementamsuper
(se houver) para fazê-lo.fonte
Como Raymond disse em sua resposta, uma ligação direta
A.__init__
eB.__init__
funciona bem, e seu código seria legível.No entanto, ele não usa o link de herança entre
C
essas classes. A exploração desse link oferece maior consistência e facilita eventuais refatorações e menos propensas a erros. Um exemplo de como fazer isso:fonte
Este artigo ajuda a explicar a herança múltipla cooperativa:
http://www.artima.com/weblogs/viewpost.jsp?thread=281127
Menciona o método útil
mro()
que mostra a ordem de resolução do método. Em sua segunda exemplo, onde você chamarsuper
emA
, asuper
chamada continua no em MRO. A próxima classe na ordem éB
, é por isso queB
o init é chamado pela primeira vez.Aqui está um artigo mais técnico do site oficial do python:
http://www.python.org/download/releases/2.3/mro/
fonte
Se você estiver multiplicando classes de subclasse de bibliotecas de terceiros, não, não há uma abordagem cega para chamar os
__init__
métodos da classe base (ou quaisquer outros métodos) que realmente funcionem, independentemente de como as classes base são programadas.super
faz possível escrever classes projetadas para implementar métodos cooperativamente como parte de múltiplas árvores de herança complexas que não precisam ser conhecidas pelo autor da classe. Mas não há como usá-lo para herdar corretamente de classes arbitrárias que podem ou não ser usadassuper
.Essencialmente, se uma classe é projetada para ser subclassificada usando
super
ou com chamadas diretas para a classe base é uma propriedade que faz parte da "interface pública" da classe e deve ser documentada como tal. Se você estiver usando bibliotecas de terceiros da maneira esperada pelo autor da biblioteca e a biblioteca tiver documentação razoável, normalmente será informado o que você deve fazer para subclassificar determinadas coisas. Caso contrário, será necessário consultar o código-fonte das classes que você está subclassificando e ver qual é a convenção de invocação de classe base. Se você estiver combinando várias classes de uma ou mais bibliotecas de terceiros de uma maneira que os autores da biblioteca ; se a classe A fizer parte de uma hierarquia usando não esperar, então não pode ser possível consistentemente invocar métodos super-classe em tudosuper
e a classe B faz parte de uma hierarquia que não usa super, então nenhuma opção é garantida para o trabalho. Você precisará descobrir uma estratégia que funcione para cada caso específico.fonte