Como o super () do Python funciona com herança múltipla?


888

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 ).


De fato, herança múltipla é o único caso em que super()é útil. Eu não recomendaria usá-lo com classes usando herança linear, onde é apenas uma sobrecarga inútil.
Bachsau

9
O @Bachsau é tecnicamente correto, uma vez que é uma sobrecarga pequena, mas super () é mais pitônico e permite refatorar e alterar o código ao longo do tempo. Use super (), a menos que você realmente precise de um método específico de classe nomeado.
Paul Whipp

2
Outro problema super()é que ele força todas as subclasses a usá-las também, enquanto que quando não estão usando super(), todos as subclasses podem decidir a si mesmas. Se um desenvolvedor que o utiliza não conhece super()ou não sabe que foi usado, podem surgir problemas com o mro que são muito difíceis de rastrear.
Bachsau

Eu encontrei praticamente cada resposta aqui confusa de uma maneira ou de outra. Você realmente se referiria aqui .
matanster 03/06

Respostas:


709

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ê definir

class Third(First, Second):
    ...

O Python começará examinando Firste, se Firstnão tiver o atributo, será analisado Second.

Essa situação se torna mais complexa quando a herança começa a cruzar caminhos (por exemplo, se Firstherdada de Second). 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:

class First(object):
    def __init__(self):
        print "first"

class Second(First):
    def __init__(self):
        print "second"

class Third(First):
    def __init__(self):
        print "third"

class Fourth(Second, Third):
    def __init__(self):
        super(Fourth, self).__init__()
        print "that's it"

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:

class First(object):
    def __init__(self):
        print "first"

class Second(First):
    def __init__(self):
        print "second"

class Third(First, Second):
    def __init__(self):
        print "third"

O ThirdMRO deve ser [First, Second]ou [Second, First]? Não há expectativa óbvia, e o Python gerará um erro:

TypeError: Error when calling the metaclass bases
    Cannot create a consistent method resolution order (MRO) for bases Second, First

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, adicionar super()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:

>>> Fourth.__mro__
(<class '__main__.Fourth'>,
 <class '__main__.Second'>, <class '__main__.Third'>,
 <class '__main__.First'>,
 <type 'object'>)

12
Torna-se mais interessante (e, sem dúvida, mais confuso) quando você começa a chamar super () em Primeiro, Segundo e Terceiro [ pastebin.com/ezTyZ5Wa ].
gatoatigrado

52
Eu acho que a falta de super ligações nas primeiras aulas é um problema muito grande com essa resposta; sem discutir como / por que esse entendimento crítico importante da pergunta está perdido.
Sam Hartman

3
Esta resposta está simplesmente errada. Sem super () chamadas nos pais, nada vai acontecer. A resposta do @ lifeless é a correta.
Cerin

8
@Cerin O objetivo deste exemplo é mostrar como o MRO é construído. O exemplo NÃO pretende imprimir "primeiro \ nsegundo \ terceiro" ou o que for. E o MRO está realmente correto: Quarto .__ mro__ == (<classe ' main .Fourth'>, <class ' main .Segundo'>, <class ' main .Third'>, <class ' main .First'>, < tipo 'object'>)
rbp 17/10/2015

2
Tanto quanto posso ver, esta resposta está faltando uma das perguntas do OP, que é "E se você quiser executar a outra?". Eu gostaria de ver a resposta para esta pergunta. Devemos apenas nomear explicitamente a classe base?
Raio

251

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:

class First(object):
    def __init__(self):
        super(First, self).__init__()
        print("first")

class Second(object):
    def __init__(self):
        super(Second, self).__init__()
        print("second")

class Third(First, Second):
    def __init__(self):
        super(Third, self).__init__()
        print("third")

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 de Second.__init__().

Isto é o que eu recebo:

>>> Third()
second
first
third

90
O que fazer se essas classes precisarem de parâmetros diferentes para se inicializarem?
calfzhou

2
"subclasse cooperativa"
Quant Metropolis

6
Dessa maneira, os métodos init de ambas as classes base serão executados, enquanto o exemplo original chama apenas o primeiro init encontrado no MRO. Eu acho que isso está implícito no termo "subclassificação cooperativa", mas um esclarecimento teria sido útil ('Explícito é melhor do que implícito', você sabe;)))
Quant Metropolis

1
Sim, se você estiver passando parâmetros diferentes para um método chamado via super, todas as implementações desse método subindo o MRO em direção a object () precisam ter assinaturas compatíveis. Isso pode ser alcançado por meio de parâmetros de palavra-chave: aceite mais parâmetros do que o método usa e ignore outros. Geralmente é considerado feio fazer isso e, na maioria dos casos, adicionar novos métodos é melhor, mas o init é (quase?) Exclusivo como um nome de método especial, mas com parâmetros definidos pelo usuário.
sem vida

15
O design da herança múltipla é realmente muito ruim em python. As classes base quase precisam saber quem a derivará e quantas outras derivarão derivarão, e em que ordem ... caso contrário, superelas falharão na execução (devido à incompatibilidade de parâmetros) ou não chamarão algumas das bases (porque você não escreveu superem uma das bases que quebra o link)!
Nawaz

186

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:

#!/usr/bin/env python2

class First(object):
  def __init__(self):
    print "First(): entering"
    super(First, self).__init__()
    print "First(): exiting"

class Second(object):
  def __init__(self):
    print "Second(): entering"
    super(Second, self).__init__()
    print "Second(): exiting"

class Third(First, Second):
  def __init__(self):
    print "Third(): entering"
    super(Third, self).__init__()
    print "Third(): exiting"

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":

Third --> First --> object --> Second --> object

Após remover todas as duplicatas, exceto a última, obtemos:

Third --> First --> Second --> object

Então, vamos seguir o que acontece quando instanciamos uma instância da Thirdclasse, por exemplo x = Third().

  1. De acordo com MRO Third.__init__executa.
    • impressões Third(): entering
    • depois super(Third, self).__init__()executa e retorna MRO First.__init__que é chamado.
  2. First.__init__ executa.
    • impressões First(): entering
    • depois super(First, self).__init__()executa e retorna MRO Second.__init__que é chamado.
  3. Second.__init__ executa.
    • impressões Second(): entering
    • depois super(Second, self).__init__()executa e retorna MRO object.__init__que é chamado.
  4. object.__init__ executa (não há instruções de impressão no código)
  5. execução remonta à Second.__init__qual, em seguida, imprimeSecond(): exiting
  6. execução remonta à First.__init__qual, em seguida, imprimeFirst(): exiting
  7. execução remonta à Third.__init__qual, em seguida, imprimeThird(): exiting

Isso detalha por que a instanciação do Third () resulta em:

Third(): entering
First(): entering
Second(): entering
Second(): exiting
First(): exiting
Third(): exiting

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!


6
Ainda não entendo o porquê: Inside init do First super (First, self) .__ init __ () chama o init do Second, porque é isso que o MRO dita!
user389955

@ user389955 O objeto criado é do tipo Terceiro, que possui todos os métodos init. Portanto, se você presumir que o MRO cria uma lista de todas as funções init em uma ordem específica, a cada super chamada, você está avançando um passo até chegar ao fim.
precisa saber é o seguinte

15
Eu acho que a Etapa 3 precisa de mais explicações: se Thirdnão herdar de Second, super(First, self).__init__chamaria object.__init__e, depois de retornar, "primeiro" seria impresso. Mas porque Thirdherda de ambos FirsteSecond , em vez de chamar object.__init__após First.__init__o MRO dita que apenas a chamada final para object.__init__é preservada, e as instruções de impressão em Firste Secondnão são alcançadas até object.__init__retornos. Desde que Secondfoi o último a ligar object.__init__, ele retorna para dentro Secondantes de retornar First.
MountainDrew

1
Curiosamente, o PyCharm parece saber tudo isso (suas dicas falam sobre quais parâmetros combinam com quais chamadas para super. Ele também tem alguma noção de covariância de entradas, por isso reconhece List[subclass]como umList[superclass]subclass uma subclasse if superclass( Listvem do typingmódulo do PEP 483 IIRC).
Reb.Cabin

Bom post, mas sinto falta de informações com relação aos argumentos dos construtores, ou seja, o que acontece se o Segundo e o Primeiro esperarem argumentos distintos? O construtor da First precisará processar alguns dos argumentos e repassar o restante para a Second. Isso está certo? Não me parece correto que o First precise conhecer os argumentos necessários para o Second.
Christian K.

58

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.


Este não é o problema do diamante. O problema do diamante envolve quatro classes e a pergunta do OP envolve apenas três.
22711 Ian Goodfellow

147
objecté o quarto
GP89 26/10/11

28

Foi 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 Aestá uma classe base extensível Be Csão as classes MixIn que fornecem função f. Ae Bambos esperam parâmetros vem seus __init__e Cesperam w. A função faceita um parâmetro y. Qherda de todas as três classes. MixInFé a interface mixin para Be C.


class A(object):
    def __init__(self, v, *args, **kwargs):
        print "A:init:v[{0}]".format(v)
        kwargs['v']=v
        super(A, self).__init__(*args, **kwargs)
        self.v = v


class MixInF(object):
    def __init__(self, *args, **kwargs):
        print "IObject:init"
    def f(self, y):
        print "IObject:y[{0}]".format(y)


class B(MixInF):
    def __init__(self, v, *args, **kwargs):
        print "B:init:v[{0}]".format(v)
        kwargs['v']=v
        super(B, self).__init__(*args, **kwargs)
        self.v = v
    def f(self, y):
        print "B:f:v[{0}]:y[{1}]".format(self.v, y)
        super(B, self).f(y)


class C(MixInF):
    def __init__(self, w, *args, **kwargs):
        print "C:init:w[{0}]".format(w)
        kwargs['w']=w
        super(C, self).__init__(*args, **kwargs)
        self.w = w
    def f(self, y):
        print "C:f:w[{0}]:y[{1}]".format(self.w, y)
        super(C, self).f(y)


class Q(C,B,A):
    def __init__(self, v, w):
        super(Q, self).__init__(v=v, w=w)
    def f(self, y):
        print "Q:f:y[{0}]".format(y)
        super(Q, self).f(y)

Eu acho que isso talvez deva ser uma pergunta e resposta separada, já que o MRO é um tópico suficientemente grande por si só, sem se envolver em argumentos variados entre funções com herança (herança múltipla é um caso especial disso).
sem vida

8
Teoricamente, sim. Na prática, esse cenário surge toda vez que eu encontro a herança Diamond em python, então a adicionei aqui. Desde então, é para onde eu vou sempre que não consigo evitar a herança de diamantes. Aqui estão alguns links extras para o meu futuro: rhettinger.wordpress.com/2011/05/26/super-considered-super code.activestate.com/recipes/…
brent.payne

O que queremos é programas com nomes de parâmetros semanticamente significativos. Porém, neste exemplo, quase todos os parâmetros são nomeados anonimamente, o que tornará muito mais difícil para o programador original documentar o código e para outro programador ler o código.
Arthur

Uma solicitação de recebimento para o repositório do github com nomes descritivos seria apreciada
brent.payne 29/11

@ brent.payne Acho @Arthur significava que toda a sua abordagem depende do uso de args/ kwargsem vez de parâmetros nomeados.
max

25

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:


class First(object):
    def __init__(self):
        print '1'

class Second(object):
    def __init__(self):
        print '2'

class Third(First, Second):
    def __init__(self):
        Second.__init__(self)

Observe que, se você fizer isso dessa maneira, precisará chamar cada um manualmente, pois tenho certeza Firstde __init__()que não será chamado.


5
Não será chamado porque você não chamou cada classe herdada. O problema é que, se Firste Secondestamos herdando outra classe e chamando-a diretamente, essa classe comum (ponto de partida do diamante) é chamada duas vezes. super está evitando isso.
Trilarion

@ Trilarion Sim, eu estava confiante de que não. No entanto, eu não sabia definitivamente e não queria declarar como se soubesse, embora isso fosse muito improvável. Esse é um bom argumento sobre objectser chamado duas vezes. Eu não pensei sobre isso. Eu só queria dizer que você chama classes de pais diretamente.
Seaux

Infelizmente, isso será interrompido se o init tentar acessar qualquer método privado :(
Erik Aronesty

21

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:

  • Filhos de uma classe vêm antes de seus pais
  • Os pais esquerdos vêm antes dos pais direitos
  • Uma classe aparece apenas uma vez na MRO

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, é:

  1. Criança
  2. Esquerda
  3. Direita
  4. Pai

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 superdentro 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 superprimeiro em cada método

class Parent(object):
    def __init__(self):
        super(Parent, self).__init__()
        print "parent"

class Left(Parent):
    def __init__(self):
        super(Left, self).__init__()
        print "left"

class Right(Parent):
    def __init__(self):
        super(Right, self).__init__()
        print "right"

class Child(Left, Right):
    def __init__(self):
        super(Child, self).__init__()
        print "child"

Child() Saídas:

parent
right
left
child

Com superúltimo em cada método

class Parent(object):
    def __init__(self):
        print "parent"
        super(Parent, self).__init__()

class Left(Parent):
    def __init__(self):
        print "left"
        super(Left, self).__init__()

class Right(Parent):
    def __init__(self):
        print "right"
        super(Right, self).__init__()

class Child(Left, Right):
    def __init__(self):
        print "child"
        super(Child, self).__init__()

Child() Saídas:

child
left
right
parent

Vejo que você pode acessar Leftusando a super()partir de Child. suponha que eu queira acessar Rightde dentro Child. Existe uma maneira de acessar Righta partir Childusando super? Ou devo ligar diretamente Rightde dentro super?
alpha_989

4
@ alpha_989 Se você deseja acessar apenas o método de uma classe específica, deve referenciá-la diretamente, em vez de usar super. Super é seguir a cadeia de herança, não chegar ao método de uma classe específica.
Zags

1
Obrigado por mencionar explicitamente 'Uma classe aparece apenas uma vez no MRO'. Isso resolveu meu problema. Agora finalmente entendo como a herança múltipla funciona. Alguém precisava mencionar as propriedades do MRO!
Tushar Vazirani

18

Sobre o comentário de @ calfzhou , você pode usar, como de costume **kwargs:

Exemplo de execução online

class A(object):
  def __init__(self, a, *args, **kwargs):
    print("A", a)

class B(A):
  def __init__(self, b, *args, **kwargs):
    super(B, self).__init__(*args, **kwargs)
    print("B", b)

class A1(A):
  def __init__(self, a1, *args, **kwargs):
    super(A1, self).__init__(*args, **kwargs)
    print("A1", a1)

class B1(A1, B):
  def __init__(self, b1, *args, **kwargs):
    super(B1, self).__init__(*args, **kwargs)
    print("B1", b1)


B1(a1=6, b1=5, b="hello", a=None)

Resultado:

A None
B hello
A1 6
B1 5

Você também pode usá-los em posição:

B1(5, 6, b="hello", a=None)

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 *argse **kwargsquando substituem um método, enquanto é um dos poucos realmente úteis e sãos dessas 'variáveis ​​mágicas'.


Uau, isso é realmente feio. É uma pena que você não possa dizer qual superclasse específica deseja chamar. Ainda assim, isso me dá ainda mais incentivo para usar a composição e evitar herança múltipla como a praga.
Tom Busby

15

Outro ponto ainda não coberto é a passagem de parâmetros para a inicialização de classes. Como o destino de superdepende 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:

class A(object):
    def __init__(self, **kwargs):
        print('A.__init__')
        super().__init__()

class B(A):
    def __init__(self, **kwargs):
        print('B.__init__ {}'.format(kwargs['x']))
        super().__init__(**kwargs)


class C(A):
    def __init__(self, **kwargs):
        print('C.__init__ with {}, {}'.format(kwargs['a'], kwargs['b']))
        super().__init__(**kwargs)


class D(B, C): # MRO=D, B, C, A
    def __init__(self):
        print('D.__init__')
        super().__init__(a=1, b=2, x=3)

print(D.mro())
D()

dá:

[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
D.__init__
B.__init__ 3
C.__init__ with 1, 2
A.__init__

Chamar a superclasse __init__diretamente para uma atribuição mais direta de parâmetros é tentador, mas falha se houver qualquer superchamada 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.


5
class First(object):
  def __init__(self, a):
    print "first", a
    super(First, self).__init__(20)

class Second(object):
  def __init__(self, a):
    print "second", a
    super(Second, self).__init__()

class Third(First, Second):
  def __init__(self):
    super(Third, self).__init__(10)
    print "that's it"

t = Third()

Saída é

first 10
second 20
that's it

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.

first 10
that's it

1
isso porque na aula Primeiro, você chamou 'print' primeiro e depois 'super'.
qi rochoso

2
que era para ilustrar a ordem de chamada
Seraj Ahmad 6/16

4

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 ()

super(First, self).__init__() #example for class First.

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:

class First(object):
    def __init__(self):

        print("first")

class Second(First):
    def __init__(self):
        print("second (before)")
        super(Second, self).__init__()
        print("second (after)")

class Third(First):
    def __init__(self):
        print("third (before)")
        super(Third, self).__init__()
        print("third (after)")


class Fourth(First):
    def __init__(self):
        print("fourth (before)")
        super(Fourth, self).__init__()
        print("fourth (after)")


class Fifth(Second, Third, Fourth):
    def __init__(self):
        print("fifth (before)")
        super(Fifth, self).__init__()
        print("fifth (after)")

Fifth()

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.

1.) print("fifth (before)")
2.) super()>[Second, Third, Fourth] (Left to right)
3.) print("second (before)")
4.) super()> First (First is the Parent which inherit from object)

O pai foi encontrado e continuará na Terceira e Quarta !!

5.) print("third (before)")
6.) super()> First (Parent class)
7.) print ("Fourth (before)")
8.) super()> First (Parent class)

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.

9.) print("first") (Parent)
10.) print ("Fourth (after)") (Class Fourth un-box)
11.) print("third (after)") (Class Third un-box)
12.) print("second (after)") (Class Second un-box)
13.) print("fifth (after)") (Class Fifth un-box)
14.) Fifth() executed

O resultado do programa acima:

fifth (before)
second (before
third (before)
fourth (before)
first
fourth (after)
third (after)
second (after)
fifth (after)

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.


Obrigado pela demonstração detalhada!
Tushar Vazirani

3

Gostaria de acrescentar o que o @Visionscaper diz no topo:

Third --> First --> object --> Second --> object

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 classe C
  • mais a mesclagem de
    • linearização de seus pais P1, P2, .. = L (P1, P2, ...) e
    • a lista de seus pais P1, P2, ..

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:

    L(O)  := [O]  // the linearization(mro) of O(object), because O has no parents

    L(First)  :=  [First] + merge(L(O), [O])
               =  [First] + merge([O], [O])
               =  [First, O]

    // Similarly, 
    L(Second)  := [Second, O]

    L(Third)   := [Third] + merge(L(First), L(Second), [First, Second])
                = [Third] + merge([First, O], [Second, O], [First, Second])
// class First is a good candidate for the first merge step, because it only appears as the head of the first and last lists
// class O is not a good candidate for the next merge step, because it also appears in the tails of list 1 and 2, 
                = [Third, First] + merge([O], [Second, O], [Second])
// class Second is a good candidate for the second merge step, because it appears as the head of the list 2 and 3
                = [Third, First, Second] + merge([O], [O])            
                = [Third, First, Second, O]

Portanto, para uma super implementação () no seguinte código:

class First(object):
  def __init__(self):
    super(First, self).__init__()
    print "first"

class Second(object):
  def __init__(self):
    super(Second, self).__init__()
    print "second"

class Third(First, Second):
  def __init__(self):
    super(Third, self).__init__()
    print "that's it"

torna-se óbvio como esse método será resolvido

Third.__init__() ---> First.__init__() ---> Second.__init__() ---> 
Object.__init__() ---> returns ---> Second.__init__() -
prints "second" - returns ---> First.__init__() -
prints "first" - returns ---> Third.__init__() - prints "that's it"

"é porque Second aparece em uma posição de cabeçalho e não aparece na posição de cauda em um subconjunto de hierarquia." Não está claro o que é uma posição inicial ou final, nem o que é um subconjunto de hierarquia ou a que subconjunto você está se referindo.
OrangeSherbet

A posição da cauda refere-se às classes que são mais altas na hierarquia de classes e vice-versa. A classe base 'objeto' está no final da cauda. A chave para entender o algoritmo mro é como 'Second' aparece como o super de 'First'. Normalmente, assumiríamos que é a classe 'objeto'. Isso é verdade, mas apenas na perspectiva da classe 'Primeira'. No entanto, quando vista da perspectiva da classe 'Terceira', a ordem da hierarquia para 'Primeira' é diferente e é calculada como mostrado acima. mro algoritmo tenta criar esta perspectiva (ou hierarquia subconjunto) para todas as classes de múltiplos herdado
supi

3

Em python, a herança 3.5+ parece previsível e muito agradável para mim. Por favor, veja este código:

class Base(object):
  def foo(self):
    print("    Base(): entering")
    print("    Base(): exiting")


class First(Base):
  def foo(self):
    print("   First(): entering Will call Second now")
    super().foo()
    print("   First(): exiting")


class Second(Base):
  def foo(self):
    print("  Second(): entering")
    super().foo()
    print("  Second(): exiting")


class Third(First, Second):
  def foo(self):
    print(" Third(): entering")
    super().foo()
    print(" Third(): exiting")


class Fourth(Third):
  def foo(self):
    print("Fourth(): entering")
    super().foo()
    print("Fourth(): exiting")

Fourth().foo()
print(Fourth.__mro__)

Saídas:

Fourth(): entering
 Third(): entering
   First(): entering Will call Second now
  Second(): entering
    Base(): entering
    Base(): exiting
  Second(): exiting
   First(): exiting
 Third(): exiting
Fourth(): exiting
(<class '__main__.Fourth'>, <class '__main__.Third'>, <class '__main__.First'>, <class '__main__.Second'>, <class '__main__.Base'>, <class 'object'>)

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


2

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.

from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required

class LoginToPost:
    @method_decorator(login_required)
    def post(self, arg, *args, **kwargs):
        super().post(arg, *args, **kwargs)

Da mesma forma para os outros métodos.

Na lista de herança de minhas classes concretas, eu adicionaria meu LoginToPost antes ListCreateAPIViewe LoginToPutOrDeleteantes RetrieveUpdateDestroyAPIView. Minhas aulas de concreto getpermaneceriam sem decoração.


1

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.

    A
   / \
  B   C
   \ /
    D

O trecho de código de exemplo seria; -

class A:
    def __init__(self, name=None):
        #  this is the head of the diamond, no need to call super() here
        self.name = name

class B(A):
    def __init__(self, param1='hello', **kwargs):
        super().__init__(**kwargs)
        self.param1 = param1

class C(A):
    def __init__(self, param2='bye', **kwargs):
        super().__init__(**kwargs)
        self.param2 = param2

class D(B, C):
    def __init__(self, works='fine', **kwargs):
        super().__init__(**kwargs)
        print(f"{works=}, {self.param1=}, {self.param2=}, {self.name=}")

d = D(name='Testing')

Aqui a classe A é object


1
Atambém deve estar ligando __init__. Anão "inventou" o método __init__, portanto não pode assumir que alguma outra classe possa ter Aanteriormente em sua MRO. A única classe cujo __init__método não chama (e não deve) chamar super().__init__é object.
chepner 8/04

Sim. É por isso que eu escrevi A é objectTalvez eu penso, eu deveria escrever class A (object) : em vez
Akhil NADH PC

ANão pode ser objectse você estiver adicionando um parâmetro ao seu __init__.
chepner 15/04
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.