web-dev-qa-db-fra.com

Comment le "super" de Python fait-il la bonne chose?

J'exécute Python 2.5, donc cette question peut ne pas s'appliquer à Python 3. Lorsque vous créez une hiérarchie de classe Diamond en utilisant l'héritage multiple et créez un objet de la classe la plus dérivée, Python fait la bonne chose (TM). Il appelle le constructeur de la classe la plus dérivée, puis ses classes parentes comme indiqué de gauche à droite, puis le grand-parent. Je connais Python MRO ; ce n'est pas ma question. Je suis curieux de voir comment l'objet renvoyé de super parvient à communiquer avec les appels de super dans le classes parent l'ordre correct. Considérez 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 fait la chose intuitive, il imprime:

D init
B init
C init
A init

Cependant, si vous commentez l'appel à super dans la fonction init de B, ni la fonction init de A ni de 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 super dans la définition init de D communique-t-il l'existence de C à l'objet renvoyé par super dans la définition init de 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 n'est-ce pas super au lieu de self.super?

Edit: Jekke a souligné à juste titre que ce n'est pas self.super parce que super est un attribut de la classe, pas une instance de la classe. Conceptuellement, cela a du sens, mais dans la pratique, le 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'attributs super ou __super__.

51
Joseph Garvin

J'ai fourni un tas de liens ci-dessous, qui répondent à votre question plus en détail et plus précisément que je ne peux l'espérer. Je répondrai cependant à votre question dans mes propres mots également, 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) dans Python a un attribut __mro__, Qui stocke l'ordre de résolution des méthodes 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 Derived ( 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 DC).
  6. Lorsque la méthode de correspondance est trouvée (disons dans la classe BC1 ), elle est appelée.
    . , à 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, self).__init__() est appelée. isinstance (self, D) => Vrai
  2. Recherchez la méthode suivante dans le MRO dans les classes à droite de D.

    B.__init__ Trouvé et appelé


  1. B.__init__ Appelle super(B, self).__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'est-à-dire que 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
Choses à surveiller lors de l'utilisation de super
http://fuhm.net/super-harmful/
Algorithme MRO Pythons:
http://www.python.org/download/releases/2.3/mro/
documents de super:
http://docs.python.org/library/functions.html
Le bas de cette page a une belle section sur super:
http://docstore.mik.ua/orelly/other/python/0596001886_pythonian-chp-5-sect-2.html

J'espère que cela aide à clarifier les choses.

15
batbrat

Changez votre code en ceci et je pense que cela expliquera les choses (vraisemblablement super regarde où, par exemple, B est 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 astucieux, mais vous ne pouvez pas l'utiliser .

34
Jacob Gabrielson

juste deviner:

self dans les quatre méthodes fait référence au même objet, c'est-à-dire de la classe D. donc, dans B.__init__(), l'appel à super(B,self) connaît toute l'ascendance diamant de self et il doit récupérer la méthode de 'after' B. dans ce cas, c'est la classe C.

6
Javier

super() connaît la hiérarchie de classe complète. C'est ce qui se passe à l'intérieur de l'init de B:

>>> super(B, self)
<super: <class 'B'>, <D object>>

Cela résout la question centrale,

comment l'objet renvoyé par super dans la définition init de D communique-t-il l'existence de C à l'objet renvoyé par super dans la définition init de B?

A savoir, dans la définition init de B, self est une instance de D, et communique ainsi l'existence de C. Par exemple, C se trouve dans type(self).__mro__.

3
hrr

La réponse de Jacob montre comment comprendre le problème, tandis que celle de batbrat montre les détails et celle des RH va droit au but.

Une chose qu'ils ne couvrent pas (du moins pas explicitement) de votre question est ce point:

Cependant, si vous commentez l'appel à super dans la fonction init de B, ni la fonction init de A ni de 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 de Bsuper(B, self).__init__() appelle en fait 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 'appel de la version de base de B de __init__'. Au lieu de cela, c'est 'appelant __init__ Sur la première classe à droite de B qui est présent sur __mro__ De self et qui a tel un attribut.

Donc, si vous commentez l'appel à super dans la fonction init de B, la pile de méthodes s'arrêtera sur B.__init__, Et n'atteindra jamais C ou A.

Résumer:

  • Quelle que soit la classe qui s'y réfère, 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 sont à droite de celle en cours sur le __mro__. Comme le __mro__ Reste constant, ce qui se passe, c'est qu'il est recherché comme une liste, pas comme un arbre ou un graphique.

Sur ce dernier point, notez que le nom complet de l'algorithme du MRO est linéarisation C3 superclasse. Autrement dit, il aplatit cette structure en une liste. Lorsque les différents appels super() se produisent, ils itèrent efficacement cette liste.

2
caxcaxcoatl