Como o "super" de Python faz a coisa certa?
-
03-07-2019 - |
Pergunta
Estou executando o Python 2.5, então essa pergunta pode não se aplicar ao Python 3. Quando você faz uma hierarquia de classe de diamante usando a herança múltipla e cria um objeto da classe mais derivada, o Python faz a coisa certa (TM). Ele chama o construtor para a classe mais derivada e, em seguida, suas classes pais listadas da esquerda para a direita e depois do avô. Estou familiarizado com o Python Mro; Essa não é a minha pergunta. Estou curioso para saber como o objeto retornou do Super realmente consegue se comunicar com chamadas de super nas classes dos pais, a ordem correta. Considere este código de exemplo:
#!/usr/bin/python
class A(object):
def __init__(self): print "A init"
class B(A):
def __init__(self):
print "B init"
super(B, self).__init__()
class C(A):
def __init__(self):
print "C init"
super(C, self).__init__()
class D(B, C):
def __init__(self):
print "D init"
super(D, self).__init__()
x = D()
O código faz a coisa intuitiva, imprime:
D init
B init
C init
A init
No entanto, se você comentar a chamada para super na função init de B, nem a função init de A nem C de C será chamada. Isso significa que o chamado de B para Super está ciente da existência de C na hierarquia geral da classe. Sei que Super retorna um objeto proxy com um operador GET sobrecarregado, mas como o objeto devolvido pela Super na definição init de D comunica a existência de C ao objeto devolvido pela Super na definição init de B? As informações de que as chamadas subsequentes do super uso são armazenadas no próprio objeto? Se sim, por que super em vez de si mesmo não é mais?
EDIT: Jekke apontou com razão que não é mais verdadeiro porque Super é um atributo da classe, não uma instância da classe. Conceitualmente, isso faz sentido, mas na prática Super não é um atributo da classe também! Você pode testar isso no intérprete fazendo duas classes A e B, onde B herda de A e chamando dir(B)
. Não tem super
ou __super__
atributos.
Solução
Eu forneci um monte de links abaixo, que respondem à sua pergunta com mais detalhes e mais precisamente do que posso esperar. No entanto, darei uma resposta à sua pergunta em minhas próprias palavras, para economizar algum tempo. Vou colocar em pontos -
- Super é uma função incorporada, não um atributo.
- Todo modelo (classe) em Python tem um
__mro__
atributo, que armazena a ordem de resolução do método dessa instância específica. - Cada chamada para super é do formulário super (tipo [, objeto-ou tipo]). Vamos supor que o segundo atributo seja um objeto no momento.
- No ponto de partida de Super Calls, o objeto é do tipo de classe derivada (diga dc).
- Super procura métodos que correspondam (no seu caso
__init__
) nas aulas do MRO, após a classe especificada como o primeiro argumento (neste caso, classes após DC). - Quando o método de correspondência é encontrado (digamos na aula BC1), é chamado.
(Este método deve usar super, por isso estou assumindo que ele - veja o super bacana de Python, mas não pode ser usado - link abaixo) que o método causa uma pesquisa no MRO da classe do objeto para o próximo método, à direita de BC1. - Enxágue a lavagem Repita até que todos os métodos sejam encontrados e chamados.
Explicação para o seu exemplo
MRO: D,B,C,A,object
super(D, self).__init__()
é chamado. isinstance (self, d) => trueProcurar por Próximo método no MRO nas aulas à direita de D.
B.__init__
encontrado e chamado
B.__init__
chamadassuper(B, self).__init__()
.isinstance (self, b) => false
isinstance (self, d) => trueAssim, o MRO é o mesmo, mas a pesquisa continua à direita do objeto B IE C, A, é pesquisada uma a uma. Nas próximas
__init__
encontrado é chamado.E assim por diante.
Uma explicação de super
http://www.python.org/download/releases/2.2.3/descrintro/#cooperation
Coisas a serem observadas ao usar super
http://fuhm.net/super-harmful/
Algoritmo MRO de Pythons:
http://www.python.org/download/releases/2.3/mro/
Docs Super:
http://docs.python.org/library/functions.html
A parte inferior desta página tem uma boa seção em Super:
http://docstore.mik.ua/orelly/other/python/0596001886_pythonian-chp-5-sect-2.html
Espero que isso ajude a esclarecer.
Outras dicas
Mude seu código para isso e acho que vai explicar as coisas (presumivelmente super
está olhando para onde, digamos, B
está no __mro__
?):
class A(object):
def __init__(self):
print "A init"
print self.__class__.__mro__
class B(A):
def __init__(self):
print "B init"
print self.__class__.__mro__
super(B, self).__init__()
class C(A):
def __init__(self):
print "C init"
print self.__class__.__mro__
super(C, self).__init__()
class D(B, C):
def __init__(self):
print "D init"
print self.__class__.__mro__
super(D, self).__init__()
x = D()
Se você executar, você verá:
D init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
B init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
C init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
A init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
Também vale a pena conferir O Super do Python é bacana, mas você não pode usá -lo.
apenas adivinhando:
self
Em todos os quatro métodos se referem ao mesmo objeto, isto é, de classe D
. Então, em B.__init__()
, a chamada para super(B,self)
conhece toda a ascendência de diamante de self
E tem que buscar o método de 'depois' B
. Nesse caso, é o C
classe.
super()
conhece o cheio Hierarquia de classe. Isso é o que acontece no Init de B:
>>> super(B, self)
<super: <class 'B'>, <D object>>
Isso resolve a questão central,
Como o objeto devolvido pela Super na definição init de D comunica a existência de C ao objeto retornado pela Super na definição init de B?
Ou seja, na definição init de B, self
é uma instância de D
, e assim comunica a existência de C
. Por exemplo C
pode ser encontrado em type(self).__mro__
.
A resposta de Jacob mostra como entender o problema, enquanto a Batbrat mostra os detalhes e a HRR vai direto ao ponto.
Uma coisa que eles não cobrem (pelo menos não explicar) de sua pergunta é este ponto:
No entanto, se você comentar a chamada para super na função init de B, nem a função init de A nem C de C será chamada.
Para entender isso, mude o código de Jacob para imprimir a pilha no início de A, como abaixo:
import traceback
class A(object):
def __init__(self):
print "A init"
print self.__class__.__mro__
traceback.print_stack()
class B(A):
def __init__(self):
print "B init"
print self.__class__.__mro__
super(B, self).__init__()
class C(A):
def __init__(self):
print "C init"
print self.__class__.__mro__
super(C, self).__init__()
class D(B, C):
def __init__(self):
print "D init"
print self.__class__.__mro__
super(D, self).__init__()
x = D()
É um pouco surpreendente ver que B
Linha super(B, self).__init__()
está realmente ligando C.__init__()
, Como C
não é uma base de base de B
.
D init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
B init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
C init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
A init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
File "/tmp/jacobs.py", line 31, in <module>
x = D()
File "/tmp/jacobs.py", line 29, in __init__
super(D, self).__init__()
File "/tmp/jacobs.py", line 17, in __init__
super(B, self).__init__()
File "/tmp/jacobs.py", line 23, in __init__
super(C, self).__init__()
File "/tmp/jacobs.py", line 11, in __init__
traceback.print_stack()
Isso acontece porque super (B, self)
não é 'Chamando a versão de Baseclass de B de B __init__
'. Em vez disso, é 'chamando __init__
na primeira aula à direita de B
que está presente em self
's __mro__
E isso tem esse atributo.
Então, se você Comente a chamada para super na função init de B, a pilha de métodos vai parar B.__init__
, e nunca chegará C
ou A
.
Para resumir:
- Independentemente de qual classe está se referindo a ela,
self
é sempre uma referência à instância, e seu__mro__
e__class__
permanece constante - Super () encontra o método olhando para as classes que estão à direita do atual
__mro__
. Enquanto o__mro__
permanece constante, o que acontece é que ele é pesquisado como uma lista, não como uma árvore ou um gráfico.
Nesse último ponto, observe que o nome completo do algoritmo do MRO é Linearização da superclasse C3. Isto é, nutre essa estrutura em uma lista. Quando o diferente super()
As chamadas acontecem, elas estão efetivamente iterando essa lista.