Como o super () do Python realmente funciona, no caso geral?

Existem muitos recursos excelentes emsuper(), Incluindoesta excelente postagem no blog que aparece muito, além de muitas perguntas sobre o Stack Overflow. No entanto, sinto que todos param de explicar como funciona no caso mais geral (com gráficos arbitrários de herança), bem como o que está acontecendo sob o capô.

Considere este exemplo básico de herança de diamantes:

class A(object):
    def foo(self):
        print 'A foo'

class B(A):
    def foo(self):
        print 'B foo before'
        super(B, self).foo()
        print 'B foo after'

class C(A):
    def foo(self):
        print 'C foo before'
        super(C, self).foo()
        print 'C foo after'

class D(B, C):
    def foo(self):
        print 'D foo before'
        super(D, self).foo()
        print 'D foo after'

Se você ler as regras do Python para a ordem de resolução de métodos de fontes comoesta ou procure opágina da wikipedia para linearização C3, você verá que o MRO deve ser(D, B, C, A, object). Obviamente, isso é confirmado porD.__mro__:

(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)

E

d = D()
d.foo()

impressões

D foo before
B foo before
C foo before
A foo
C foo after
B foo after
D foo after

que corresponde ao MRO. No entanto, considere que acimasuper(B, self).foo() noB na verdade chamaC.foo, enquanto que emb = B(); b.foo() simplesmente iria direto paraA.foo. Claramente usandosuper(B, self).foo() não é simplesmente um atalho paraA.foo(self) como às vezes é ensinado.

super() fica obviamente ciente das chamadas anteriores e do MRO geral que a cadeia está tentando seguir. Eu posso ver duas maneiras pelas quais isso pode ser feito. O primeiro é fazer algo como passar osuper próprio objeto como oself argumento para o próximo método na cadeia, que atuaria como o objeto original, mas também conteria essas informações. No entanto, isso também parece quebrar muitas coisas (super(D, d) is d é falso) e, ao fazer algumas experiências, vejo que esse não é o caso.

A outra opção é ter algum tipo de contexto global que armazena o MRO e a posição atual nele. Eu imagino o algoritmo parasuper vai algo como:

Existe atualmente um contexto em que estamos trabalhando? Caso contrário, crie um que contenha uma fila. Obtenha o MRO para o argumento de classe, envie todos os elementos, exceto o primeiro, para a fila.Exiba o próximo elemento da fila MRO do contexto atual, use-o como a classe atual ao construir osuper instância.Quando um método é acessado a partir dosuper exemplo, procure-o na classe atual e chame-o usando o mesmo contexto.

No entanto, isso não explica coisas estranhas, como usar uma classe base diferente como o primeiro argumento para uma chamada parasuper, ou mesmo chamando um método diferente. Eu gostaria de saber o algoritmo geral para isso. Além disso, se esse contexto existir em algum lugar, posso inspecioná-lo? Posso estragar com isso? É uma péssima idéia, é claro, mas o Python normalmente espera que você seja um adulto maduro, mesmo que não o seja.

Isso também introduz muitas considerações de design. Se eu escrevesseB pensando apenas em sua relação comAe depois alguém escreveC e uma terceira pessoa escreveD, meuB.foo() método tem que chamarsuper de uma maneira que seja compatível comC.foo() mesmo que não existisse no momento em que escrevi! Se eu quiser que minha classe seja facilmente extensível, precisarei explicar isso, mas não tenho certeza se é mais complicado do que simplesmente garantir que todas as versões dofoo possuem assinaturas idênticas. Há também a questão de quando colocar o código antes ou depois da chamada parasuper, mesmo que não faça nenhuma diferença considerandoBapenas classes básicas.

questionAnswers(1)

yourAnswerToTheQuestion