Eu sou praticamente novo em programação orientada a objetos Python e tenho problemas para entender a super()
função (novas classes de estilo), especialmente quando se trata de herança múltipla.
Por exemplo, se você tiver algo como:
class First(object):
def __init__(self):
print "first"
class Second(object):
def __init__(self):
print "second"
class Third(First, Second):
def __init__(self):
super(Third, self).__init__()
print "that's it"
O que não entendo é: a Third()
classe herdará os dois métodos construtores? Se sim, qual será executado com super () e por quê?
E se você quiser executar o outro? Eu sei que tem algo a ver com a ordem de resolução de método Python ( MRO ).
python
multiple-inheritance
Calisto
fonte
fonte
super()
é útil. Eu não recomendaria usá-lo com classes usando herança linear, onde é apenas uma sobrecarga inútil.super()
é que ele força todas as subclasses a usá-las também, enquanto que quando não estão usandosuper()
, todos as subclasses podem decidir a si mesmas. Se um desenvolvedor que o utiliza não conhecesuper()
ou não sabe que foi usado, podem surgir problemas com o mro que são muito difíceis de rastrear.Respostas:
Isso é detalhado com uma quantidade razoável de detalhes pelo próprio Guido em sua postagem no blog Ordem de resolução de métodos (incluindo duas tentativas anteriores).
No seu exemplo,
Third()
ligaráFirst.__init__
. O Python procura cada atributo nos pais da classe, conforme listados da esquerda para a direita. Neste caso, estamos procurando__init__
. Então, se você definirO Python começará examinando
First
e, seFirst
não tiver o atributo, será analisadoSecond
.Essa situação se torna mais complexa quando a herança começa a cruzar caminhos (por exemplo, se
First
herdada deSecond
). Leia o link acima para obter mais detalhes, mas, em poucas palavras, o Python tentará manter a ordem em que cada classe aparece na lista de herança, começando com a própria classe filho.Então, por exemplo, se você tivesse:
o MRO seria
[Fourth, Second, Third, First].
A propósito: se o Python não conseguir encontrar uma ordem de resolução de método coerente, isso criará uma exceção, em vez de voltar ao comportamento que pode surpreender o usuário.
Editado para adicionar um exemplo de uma MRO ambígua:
O
Third
MRO deve ser[First, Second]
ou[Second, First]
? Não há expectativa óbvia, e o Python gerará um erro:Edit: Eu vejo várias pessoas argumentando que os exemplos acima não têm
super()
chamadas, então deixe-me explicar: O objetivo dos exemplos é mostrar como o MRO é construído. Eles não pretendem imprimir "primeiro \ nsegundo \ terceiro" ou o que for. Você pode - e deve, é claro, brincar com o exemplo, adicionarsuper()
chamadas, ver o que acontece e obter uma compreensão mais profunda do modelo de herança do Python. Mas meu objetivo aqui é simplificar e mostrar como o MRO é construído. E é construído como expliquei:fonte
Seu código e as outras respostas são todas de buggy. Estão faltando as
super()
chamadas nas duas primeiras classes necessárias para que a subclasse cooperativa funcione.Aqui está uma versão fixa do código:
A
super()
chamada localiza o próximo método no MRO a cada etapa, e é por isso que o Primeiro e o Segundo também precisam dele, caso contrário, a execução é interrompida no final deSecond.__init__()
.Isto é o que eu recebo:
fonte
super
elas falharão na execução (devido à incompatibilidade de parâmetros) ou não chamarão algumas das bases (porque você não escreveusuper
em uma das bases que quebra o link)!Eu queria elaborar a resposta um pouco sem vida, porque quando comecei a ler sobre como usar super () em uma hierarquia de herança múltipla no Python, não a recebi imediatamente.
O que você precisa entender é que
super(MyClass, self).__init__()
fornece o próximo__init__
método de acordo com o algoritmo MRO (Method Resolution Ordering) usado no contexto da hierarquia de herança completa .Esta última parte é crucial para entender. Vamos considerar o exemplo novamente:
De acordo com este artigo sobre a Ordem de resolução de método de Guido van Rossum, a ordem de resolução
__init__
é calculada (antes do Python 2.3) usando um "percurso de profundidade da esquerda para a direita":Após remover todas as duplicatas, exceto a última, obtemos:
Então, vamos seguir o que acontece quando instanciamos uma instância da
Third
classe, por exemplox = Third()
.Third.__init__
executa.Third(): entering
super(Third, self).__init__()
executa e retorna MROFirst.__init__
que é chamado.First.__init__
executa.First(): entering
super(First, self).__init__()
executa e retorna MROSecond.__init__
que é chamado.Second.__init__
executa.Second(): entering
super(Second, self).__init__()
executa e retorna MROobject.__init__
que é chamado.object.__init__
executa (não há instruções de impressão no código)Second.__init__
qual, em seguida, imprimeSecond(): exiting
First.__init__
qual, em seguida, imprimeFirst(): exiting
Third.__init__
qual, em seguida, imprimeThird(): exiting
Isso detalha por que a instanciação do Third () resulta em:
O algoritmo MRO foi aprimorado a partir do Python 2.3 em diante para funcionar bem em casos complexos, mas acho que o uso da "profundidade da primeira travessia da esquerda para a direita" + "removendo duplicatas esperadas pelo último" ainda funciona na maioria dos casos (por favor comentar se este não for o caso). Não deixe de ler a postagem do blog de Guido!
fonte
Third
não herdar deSecond
,super(First, self).__init__
chamariaobject.__init__
e, depois de retornar, "primeiro" seria impresso. Mas porqueThird
herda de ambosFirst
eSecond
, em vez de chamarobject.__init__
apósFirst.__init__
o MRO dita que apenas a chamada final paraobject.__init__
é preservada, e as instruções de impressão emFirst
eSecond
não são alcançadas atéobject.__init__
retornos. Desde queSecond
foi o último a ligarobject.__init__
, ele retorna para dentroSecond
antes de retornarFirst
.List[subclass]
como umList[superclass]
subclass
uma subclasse ifsuperclass
(List
vem dotyping
módulo do PEP 483 IIRC).Isso é conhecido como Problema do Diamante , a página possui uma entrada no Python, mas, em resumo, o Python chamará os métodos da superclasse da esquerda para a direita.
fonte
object
é o quartoFoi assim que resolvi emitir várias heranças com variáveis diferentes para inicialização e ter várias MixIns com a mesma chamada de função. Eu tive que adicionar explicitamente variáveis aos passados ** kwargs e adicionar uma interface MixIn para ser um ponto final para super chamadas.
Aqui
A
está uma classe base extensívelB
eC
são as classes MixIn que fornecem funçãof
.A
eB
ambos esperam parâmetrosv
em seus__init__
eC
esperamw
. A funçãof
aceita um parâmetroy
.Q
herda de todas as três classes.MixInF
é a interface mixin paraB
eC
.fonte
args
/kwargs
em vez de parâmetros nomeados.Entendo que isso não responde diretamente à
super()
pergunta, mas acho que é relevante o suficiente para compartilhar.Também existe uma maneira de chamar diretamente cada classe herdada:
Observe que, se você fizer isso dessa maneira, precisará chamar cada um manualmente, pois tenho certeza
First
de__init__()
que não será chamado.fonte
First
eSecond
estamos herdando outra classe e chamando-a diretamente, essa classe comum (ponto de partida do diamante) é chamada duas vezes. super está evitando isso.object
ser chamado duas vezes. Eu não pensei sobre isso. Eu só queria dizer que você chama classes de pais diretamente.No geral
Supondo que tudo descende
object
(você está por conta própria, se não o fizer), o Python calcula uma ordem de resolução de método (MRO) com base na sua árvore de herança de classe. O MRO satisfaz 3 propriedades:Se não houver essa ordem, ocorrerão erros de Python. O funcionamento interno disso é uma Linerização C3 da ancestralidade das classes. Leia tudo sobre isso aqui: https://www.python.org/download/releases/2.3/mro/
Assim, nos dois exemplos abaixo, é:
Quando um método é chamado, a primeira ocorrência desse método no MRO é aquela que é chamada. Qualquer classe que não implementa esse método é ignorada. Qualquer chamada para
super
dentro desse método chamará a próxima ocorrência desse método no MRO. Consequentemente, é importante qual a ordem em que você coloca as classes na herança e onde você faz as chamadas parasuper
nos métodos.Com o
super
primeiro em cada métodoChild()
Saídas:Com
super
último em cada métodoChild()
Saídas:fonte
Left
usando asuper()
partir deChild
. suponha que eu queira acessarRight
de dentroChild
. Existe uma maneira de acessarRight
a partirChild
usando super? Ou devo ligar diretamenteRight
de dentrosuper
?Sobre o comentário de @ calfzhou , você pode usar, como de costume
**kwargs
:Exemplo de execução online
Resultado:
Você também pode usá-los em posição:
mas você precisa se lembrar do MRO, é realmente confuso.
Eu posso ser um pouco chato, mas notei que as pessoas sempre se esqueciam de usar
*args
e**kwargs
quando substituem um método, enquanto é um dos poucos realmente úteis e sãos dessas 'variáveis mágicas'.fonte
Outro ponto ainda não coberto é a passagem de parâmetros para a inicialização de classes. Como o destino de
super
depende da subclasse, a única boa maneira de passar parâmetros é empacotá-los todos juntos. Cuidado para não ter o mesmo nome de parâmetro com significados diferentes.Exemplo:
dá:
Chamar a superclasse
__init__
diretamente para uma atribuição mais direta de parâmetros é tentador, mas falha se houver qualquersuper
chamada em uma superclasse e / ou o MRO for alterado e a classe A puder ser chamada várias vezes, dependendo da implementação.Para concluir: herança cooperativa e parâmetros super e específicos para inicialização não estão funcionando muito bem juntos.
fonte
Saída é
Call to Third () localiza o init definido em Third. E ligar para super nessa rotina chama init definido em First. MRO = [Primeiro, Segundo]. Agora, chamar super em init definido em Primeiro continuará pesquisando o MRO e encontrará init definido em Segundo, e qualquer chamada para super atingirá o objeto padrão init . Espero que este exemplo esclareça o conceito.
Se você não chamar super de First. A corrente para e você obtém a seguinte saída.
fonte
No aprendizado desta maneira, aprendo algo chamado super (), uma função incorporada, se não estiver enganado. Chamar a função super () pode ajudar a herança a passar pelos pais e 'irmãos' e ajudá-lo a ver com mais clareza. Ainda sou iniciante, mas adoro compartilhar minha experiência em usar esse super () no python2.7.
Se você leu os comentários desta página, ouvirá sobre MRO (Method Resolution Order), sendo o método a função que você escreveu, o MRO usará o esquema Profundidade-Primeira-Esquerda-para-Direita para pesquisar e executar. Você pode fazer mais pesquisas sobre isso.
Adicionando a função super ()
Você pode conectar várias instâncias e 'famílias' com super (), adicionando a cada uma delas. E ele executará os métodos, passará por eles e verifique se você não perdeu! No entanto, adicioná-los antes ou depois faz a diferença, você saberá se fez o aprendizado durante o exercício 44. Deixe a diversão começar !!
Tomando o exemplo abaixo, você pode copiar e colar e tentar executá-lo:
Como funciona? A instância do quinto () será assim. Cada passo vai de uma aula para outra na qual a super função foi adicionada.
O pai foi encontrado e continuará na Terceira e Quarta !!
Agora todas as classes com super () foram acessadas! A classe pai foi encontrada e executada e agora continua a desmarcar a função nas heranças para concluir os códigos.
O resultado do programa acima:
Para mim, adicionar super () me permite ver mais claramente como o python executaria minha codificação e garantir que a herança possa acessar o método que eu pretendia.
fonte
Gostaria de acrescentar o que o @Visionscaper diz no topo:
Nesse caso, o intérprete não filtra a classe de objeto porque está duplicada, e sim porque Second aparece em uma posição de cabeçalho e não aparece na posição de cauda em um subconjunto de hierarquia. Embora o objeto apareça apenas nas posições da cauda e não seja considerado uma posição forte no algoritmo C3 para determinar a prioridade.
A linearização (mro) de uma classe C, L (C) é a
A mesclagem linearizada é feita selecionando as classes comuns que aparecem como o cabeçalho das listas e não a cauda, uma vez que a ordem é importante (ficará claro abaixo)
A linearização da Third pode ser calculada da seguinte forma:
Portanto, para uma super implementação () no seguinte código:
torna-se óbvio como esse método será resolvido
fonte
Em python, a herança 3.5+ parece previsível e muito agradável para mim. Por favor, veja este código:
Saídas:
Como você pode ver, ele chama exatamente uma vez para cada cadeia herdada na mesma ordem em que foi herdada. Você pode obter esse pedido ligando para . mro :
Quarto -> Terceiro -> Primeiro -> Segundo -> Base -> objeto
fonte
Talvez ainda exista algo que possa ser adicionado, um pequeno exemplo com o Django rest_framework e decoradores. Isso fornece uma resposta para a pergunta implícita: "por que eu iria querer isso de qualquer maneira?"
Como dito: estamos com o Django rest_framework e estamos usando visualizações genéricas, e para cada tipo de objeto em nosso banco de dados nos encontramos com uma classe view fornecendo GET e POST para listas de objetos, e outra classe view fornecendo GET , PUT e DELETE para objetos individuais.
Agora, o POST, PUT e DELETE queremos decorar com o login_required do Django. Observe como isso afeta as duas classes, mas nem todos os métodos nas duas classes.
Uma solução pode passar por herança múltipla.
Da mesma forma para os outros métodos.
Na lista de herança de minhas classes concretas, eu adicionaria meu
LoginToPost
antesListCreateAPIView
eLoginToPutOrDelete
antesRetrieveUpdateDestroyAPIView
. Minhas aulas de concretoget
permaneceriam sem decoração.fonte
Publicando esta resposta para minha referência futura.
A herança múltipla do Python deve usar um modelo de diamante e a assinatura da função não deve mudar no modelo.
O trecho de código de exemplo seria; -
Aqui a classe A é
object
fonte
A
também deve estar ligando__init__
.A
não "inventou" o método__init__
, portanto não pode assumir que alguma outra classe possa terA
anteriormente em sua MRO. A única classe cujo__init__
método não chama (e não deve) chamarsuper().__init__
éobject
.object
Talvez eu penso, eu deveria escreverclass A (object) :
em vezA
Não pode serobject
se você estiver adicionando um parâmetro ao seu__init__
.