Comment le «super» Python fait-il ce qu'il faut?
-
03-07-2019 - |
Question
J'utilise Python 2.5, de sorte que cette question peut ne pas s'appliquer à Python 3. Lorsque vous créez une hiérarchie de classes diamant à l'aide de l'héritage multiple et créez un objet de la classe la plus dérivée, Python exécute la tâche la plus simple. Il appelle le constructeur de la classe la plus dérivée, puis ses classes parentes répertoriées de gauche à droite, puis le grand-parent. Je connais les MRO de Python; ce n'est pas ma question. Je suis curieux de savoir comment l'objet renvoyé par super parvient effectivement à communiquer le bon ordre aux appels de super dans les classes parentes. Considérons cet exemple de code:
#!/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()
Le code est intuitif, il affiche:
D init
B init
C init
A init
Cependant, si vous commentez l'appel de la fonction init de super dans B, ni la fonction init de A ni C n'est appelée. Cela signifie que l'appel de B à super est en quelque sorte conscient de l'existence de C dans la hiérarchie globale des classes. Je sais que super renvoie un objet proxy avec un opérateur get surchargé, mais comment l'objet renvoyé par la définition d'initialisation de super dans D communique-t-il l'existence de C à l'objet renvoyé par la définition d'init de super dans B? Les informations que les appels ultérieurs de super-utilisation sont-elles stockées sur l'objet lui-même? Si c'est le cas, pourquoi super-self self-super?
Edit: Jekke a fait remarquer à juste titre que ce n’est pas self.super, car super est un attribut de la classe, pas une instance de la classe. Conceptuellement, cela a du sens, mais en pratique, super n'est pas non plus un attribut de la classe! Vous pouvez tester cela dans l'interpréteur en créant deux classes A et B, où B hérite de A et en appelant dir (B)
. Il n'a pas d'attribut super
ou __ super __
.
La solution
J'ai fourni une série de liens ci-dessous, qui répondent à votre question plus en détail et avec plus de précision que je ne pourrais jamais l'espérer. Je vais cependant répondre à votre question avec mes propres mots pour vous faire gagner du temps. Je vais le mettre en points -
- super est une fonction intégrée, pas un attribut.
- Chaque type (classe) en Python a un attribut
__ mro __
, qui stocke l'ordre de résolution de la méthode de cette instance particulière. - Chaque appel à super est de la forme super (type [, objet-ou-type]). Supposons que le deuxième attribut soit un objet pour le moment.
- Au point de départ des super-appels, l'objet est du type de la classe dérivée ( say DC ).
- super recherche les méthodes qui correspondent (dans votre cas
__ init __
) dans les classes du MRO, après la classe spécifiée comme premier argument (dans ce cas, les classes après le contrôleur de domaine). - Lorsque la méthode de correspondance est trouvée (par exemple, dans la classe BC1 ), elle s'appelle.
(Cette méthode devrait utiliser super, donc je suppose qu'elle le fait - Voir super est chouette de Python mais ne peut pas être utilisé - lien ci-dessous) Cette méthode provoque ensuite une recherche dans la MRO de la classe de l'objet pour la méthode suivante, à droite de BC1 . - Rincez le lavage jusqu'à ce que toutes les méthodes soient trouvées et appelées.
Explication de votre exemple
MRO: D,B,C,A,object
-
super (D, auto) .__ init __ ()
est appelé. isinstance (self, D) = > Vrai -
Recherchez la méthode suivante dans le MRO dans les classes situées à droite de D.
B .__ init __
trouvé et appelé
-
B .__ init __
appellesuper (B, auto) .__ init __ ()
.isinstance (self, B) = > Faux
isinstance (self, D) = > True -
Ainsi, le MRO est le même, mais la recherche continue à droite de B, c.-à-d. C, A, les objets sont recherchés un par un. Le prochain
__ init __
trouvé est appelé. -
Et ainsi de suite.
Une explication de super
http://www.python.org/download/releases/2.2 .3 / descrintro / # cooperation
Éléments à surveiller lors de l'utilisation de super
http://fuhm.net/super-harmful/
Algorithme MRO Pythons:
http://www.python.org/download/releases/2.3/mro/ / a>
Documents de super:
http://docs.python.org/library/functions.html
Le bas de cette page contient une belle section sur super:
http://docstore.mik.ua/ orelly / other / python / 0596001886_pythonian-chp-5-sect-2.html
J'espère que cela vous aidera à résoudre le problème.
Autres conseils
Modifiez votre code en ceci et je pense que cela expliquera des choses (vraisemblablement, super
recherche où, par exemple, B
se trouve dans le __ 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()
Si vous l'exécutez, vous verrez:
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'>)
Cela vaut également la peine de vérifier Python's Super est chouette, mais vous ne pouvez pas l'utiliser . / p>
juste deviner:
self
dans les quatre méthodes, fait référence au même objet, c'est-à-dire de classe D
.
Ainsi, dans B .__ init __ ()
, l’appel à super (B, auto)
connaît l’ascendance entière du losange de auto
et il a chercher la méthode 'après' B
. dans ce cas, c'est la classe C
.
super ()
connaît la hiérarchie de classe complète . Voici ce qui se passe à l'intérieur de l'init de B:
>>> super(B, self)
<super: <class 'B'>, <D object>>
Ceci résout la question centrale,
comment l'objet renvoyé par la définition d'initialisation de super dans D communique-t-il l'existence de C à l'objet renvoyé par la définition d'initialisation de super dans B?
À savoir, dans la définition d'initialisation de B, self
est une instance de D
et communique ainsi l'existence de C
. Par exemple, C
peut être trouvé dans le type (self) .__ mro __
.
La réponse de Jacob montre comment comprendre le problème, tandis que celle de batbrat en montre les détails et que les chiffres vont droit au but.
Une chose qu'ils ne traitent pas (du moins pas explicitement) de votre question est la suivante:
Cependant, si vous commentez l'appel de la fonction init de super dans B, ni la fonction init de A ni C n'est appelée.
Pour comprendre cela, changez le code de Jacob pour imprimer la pile sur l'init de A, comme ci-dessous:
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()
Il est un peu surprenant de voir que la ligne super (B, self) .__ de initia __ ()
de B
appelle en réalité C .__ init __ ()
, car C
n'est pas une classe 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()
Cela se produit car super (B, self)
n'est pas " en appelant la version de base de B du code __ init __
". Au lieu de cela, c'est ' qui appelle __ init __
sur la première classe à droite de B
qui est présent sur de
et qui possède un tel attribut . self
__mro __
Donc, si vous commentez l'appel de la fonction init de super in B , la pile de méthodes s'arrêtera sur B .__ init __
et n'atteindra jamais C
ou A
.
Pour résumer:
- Quelle que soit la classe qui y fait référence,
self
est toujours une référence à l'instance et ses__ mro __
et__ class __
restent constants - super () trouve la méthode en regardant les classes qui se trouvent à droite de celle en cours sur le
__ mro __
. Comme le__ mro __
reste constant, la recherche s'effectue sous forme de liste, et non sous forme d'arborescence ou de graphique.
Sur ce dernier point, notez que le nom complet de l'algorithme de la MRO est Linéarisation de la super-classe C3 . Autrement dit, cela aplatit cette structure dans une liste. Lorsque les différents appels super ()
se produisent, ils itèrent effectivement cette liste.