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

Était-ce utile?

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 -

  1. super est une fonction intégrée, pas un attribut.
  2. 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.
  3. Chaque appel à super est de la forme super (type [, objet-ou-type]). Supposons que le deuxième attribut soit un objet pour le moment.
  4. Au point de départ des super-appels, l'objet est du type de la classe dérivée ( say DC ).
  5. 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).
  6. 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 .
  7. 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  
  1. super (D, auto) .__ init __ () est appelé. isinstance (self, D) = > Vrai
  2. Recherchez la méthode suivante dans le MRO dans les classes situées à droite de D.

    B .__ init __ trouvé et appelé

  1. B .__ init __ appelle super (B, auto) .__ init __ () .

    isinstance (self, B) = > Faux
    isinstance (self, D) = > True

  2. 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é.

  3. 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 self __mro __ et qui possède un tel attribut .

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.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top