Question Comment super () de Python fonctionne avec l'héritage multiple?


Je suis assez nouveau dans la programmation orientée objet Python et j'ai des problèmes comprendre le super() function (nouvelles classes de style) en particulier quand il s'agit de l'héritage multiple.

Par exemple si vous avez quelque chose comme:

class First(object):
    def __init__(self):
        print "first"

class Second(object):
    def __init__(self):
        print "second"

class Third(First, Second):
    def __init__(self):
        super(Third, self).__init__()
        print "that's it"

Ce que je ne comprends pas, c'est que Third() classe hérite des deux méthodes de constructeur? Si oui, alors lequel sera exécuté avec super () et pourquoi?

Et si vous voulez lancer l'autre? Je sais que cela a quelque chose à voir avec l'ordre de résolution de la méthode Python (MRO).


610
2017-07-18 21:40


origine


Réponses:


Ceci est détaillé avec une quantité raisonnable de détails par Guido lui-même dans son blog Ordre de résolution de méthode (y compris deux tentatives précédentes).

Dans votre exemple, Third() appellera First.__init__. Python recherche chaque attribut dans les parents de la classe car ils sont listés de gauche à droite. Dans ce cas, nous recherchons __init__. Donc, si vous définissez

class Third(First, Second):
    ...

Python va commencer par regarder First, et si First n'a pas l'attribut, alors il va regarder Second.

Cette situation devient plus complexe lorsque l'héritage commence à croiser des chemins (par exemple si First hérité de Second). Lisez le lien ci-dessus pour plus de détails, mais, en un mot, Python va essayer de maintenir l'ordre dans lequel chaque classe apparaît dans la liste d'héritage, en commençant par la classe enfant elle-même.

Ainsi, par exemple, si vous aviez:

class First(object):
    def __init__(self):
        print "first"

class Second(First):
    def __init__(self):
        print "second"

class Third(First):
    def __init__(self):
        print "third"

class Fourth(Second, Third):
    def __init__(self):
        super(Fourth, self).__init__()
        print "that's it"

le MRO serait [Fourth, Second, Third, First].

D'ailleurs: si Python ne trouve pas un ordre de résolution de méthode cohérent, il déclenchera une exception, au lieu de retomber dans un comportement qui pourrait surprendre l'utilisateur.

Edité pour ajouter un exemple d'un MRO ambigu:

class First(object):
    def __init__(self):
        print "first"

class Second(First):
    def __init__(self):
        print "second"

class Third(First, Second):
    def __init__(self):
        print "third"

Devrait Third's MRO soit [First, Second] ou [Second, First]? Il n'y a pas d'attente évidente, et Python va générer une erreur:

TypeError: Error when calling the metaclass bases
    Cannot create a consistent method resolution order (MRO) for bases Second, First

Modifier: Je vois plusieurs personnes prétendre que les exemples ci-dessus manquent super() appels, alors laissez-moi vous expliquer: Le but des exemples est de montrer comment le MRO est construit. Elles sont ne pas destiné à imprimer "premier \ nsecond \ tiers" ou quoi que ce soit. Vous pouvez - et devriez, bien sûr, jouer avec l'exemple, ajouter super() appels, voir ce qui se passe, et acquérir une meilleure compréhension du modèle d'héritage de Python. Mais mon objectif ici est de rester simple et de montrer comment le MRO est construit. Et il est construit comme je l'ai expliqué:

>>> Fourth.__mro__
(<class '__main__.Fourth'>,
 <class '__main__.Second'>, <class '__main__.Third'>,
 <class '__main__.First'>,
 <type 'object'>)

502
2017-07-18 21:52



Votre code, et les autres réponses, sont tous bogués. Ils manquent le super() appelle dans les deux premières classes qui sont nécessaires pour que la sous-classe coopérative fonctionne.

Voici une version corrigée du code:

class First(object):
    def __init__(self):
        super(First, self).__init__()
        print("first")

class Second(object):
    def __init__(self):
        super(Second, self).__init__()
        print("second")

class Third(First, Second):
    def __init__(self):
        super(Third, self).__init__()
        print("third")

le super() call trouve la méthode suivante dans le MRO à chaque étape, ce qui explique pourquoi First et Second doivent l'avoir aussi, sinon l'exécution s'arrête à la fin de Second.__init__().

C'est ce que je reçois:

>>> Third()
second
first
third

177
2018-04-30 23:54



Je voulais élaborer la réponse par sans vie un peu parce que quand j'ai commencé à lire sur comment utiliser super () dans une hiérarchie d'héritage multiple en Python, je ne l'ai pas eu tout de suite.

Ce que vous devez comprendre, c'est que super(MyClass, self).__init__()fournit le prochain  __init__ méthode selon l'algorithme MRO (Method Resolution Ordering) utilisé dans le contexte de la hiérarchie d'héritage complète.

Cette dernière partie est cruciale à comprendre. Considérons à nouveau l'exemple:

class First(object):
  def __init__(self):
    super(First, self).__init__()
    print "first"

class Second(object):
  def __init__(self):
    super(Second, self).__init__()
    print "second"

class Third(First, Second):
  def __init__(self):
    super(Third, self).__init__()
    print "that's it"

Selon cet article sur l'ordre de résolution des méthodes par Guido van Rossum, l'ordre de résoudre __init__ est calculé (avant Python 2.3) en utilisant une "traversée de gauche à droite en profondeur":

Third --> First --> object --> Second --> object

Après avoir enlevé tous les doublons, sauf pour le dernier, nous obtenons:

Third --> First --> Second --> object

Alors, suivons ce qui se passe quand nous instancions une instance du Third classe, par ex. x = Third().

  1. Selon MRO __init__ de Troisième est appelée en premier.

  2. Ensuite, selon le MRO, à l'intérieur du __init__ méthode super(Third, self).__init__() résout à la __init__ méthode de Premier, qui est appelé.

  3. À l'intérieur __init__ de premier super(First, self).__init__() appelle le __init__ de Deuxièmement, parce que c'est ce que le MRO dicte!

  4. À l'intérieur __init__ de Deuxième super(Second, self).__init__() appels la __init__ d'objet, ce qui ne vaut rien. Après ça "second" est imprimé.

  5. Après super(First, self).__init__() terminé, "premier" est imprimé.

  6. Après super(Third, self).__init__() terminé, "c'est ça" est imprimé.

Cela explique pourquoi l'instanciation de Third () entraîne:

>>> x = Third()
second
first
that's it

L'algorithme MRO a été amélioré à partir de Python 2.3 pour bien fonctionner dans des cas complexes, mais je suppose que l'utilisation de la "profondeur gauche-droite" + "suppression des doublons attendus pour le dernier" fonctionne toujours dans la plupart des cas commenter si ce n'est pas le cas). Assurez-vous de lire le blog de Guido!


122
2018-05-12 09:51



Ceci est connu comme le Problème de diamant, la page a une entrée sur Python, mais en bref, Python appellera les méthodes de la superclasse de gauche à droite.


45
2017-07-18 21:50



C'est à la façon dont j'ai résolu d'avoir plusieurs héritages avec différentes variables pour l'initialisation et avoir plusieurs MixIn avec le même appel de fonction. J'ai dû ajouter explicitement des variables aux kwargs ** passés et ajouter une interface MixIn pour être un point de terminaison pour les super-appels.

Ici A est une classe de base extensible et B et C sont les classes MixIn à la fois qui fournissent la fonction f. A et B les deux attendent paramètre v dans leurs __init__ et C attend w. La fonction f prend un paramètre y. Q hérite de toutes les trois classes. MixInF est l'interface mixin pour B et C.


class A(object):
    def __init__(self, v, *args, **kwargs):
        print "A:init:v[{0}]".format(v)
        kwargs['v']=v
        super(A, self).__init__(*args, **kwargs)
        self.v = v


class MixInF(object):
    def __init__(self, *args, **kwargs):
        print "IObject:init"
    def f(self, y):
        print "IObject:y[{0}]".format(y)


class B(MixInF):
    def __init__(self, v, *args, **kwargs):
        print "B:init:v[{0}]".format(v)
        kwargs['v']=v
        super(B, self).__init__(*args, **kwargs)
        self.v = v
    def f(self, y):
        print "B:f:v[{0}]:y[{1}]".format(self.v, y)
        super(B, self).f(y)


class C(MixInF):
    def __init__(self, w, *args, **kwargs):
        print "C:init:w[{0}]".format(w)
        kwargs['w']=w
        super(C, self).__init__(*args, **kwargs)
        self.w = w
    def f(self, y):
        print "C:f:w[{0}]:y[{1}]".format(self.w, y)
        super(C, self).f(y)


class Q(C,B,A):
    def __init__(self, v, w):
        super(Q, self).__init__(v=v, w=w)
    def f(self, y):
        print "Q:f:y[{0}]".format(y)
        super(Q, self).f(y)

21
2017-08-17 19:22



Je comprends que cela ne répond pas directement à la super() question, mais je pense que c'est assez pertinent pour partager.

Il existe également un moyen d'appeler directement chaque classe héritée:


class First(object):
    def __init__(self):
        print '1'

class Second(object):
    def __init__(self):
        print '2'

class Third(First, Second):
    def __init__(self):
        Second.__init__(self)

Notez juste que si vous le faites de cette façon, vous devrez appeler chacun manuellement car je suis assez sûr Firstde __init__() ne sera pas appelé.


15
2018-01-19 17:22



Sur @ Commentaire de calfzhou, vous pouvez utiliser, comme d'habitude, **kwargs:

Exemple de course en ligne

class A(object):
  def __init__(self, a, *args, **kwargs):
    print("A", a)

class B(A):
  def __init__(self, b, *args, **kwargs):
    super(B, self).__init__(*args, **kwargs)
    print("B", b)

class A1(A):
  def __init__(self, a1, *args, **kwargs):
    super(A1, self).__init__(*args, **kwargs)
    print("A1", a1)

class B1(A1, B):
  def __init__(self, b1, *args, **kwargs):
    super(B1, self).__init__(*args, **kwargs)
    print("B1", b1)


B1(a1=6, b1=5, b="hello", a=None)

Résultat:

A None
B hello
A1 6
B1 5

Vous pouvez également les utiliser en position:

B1(5, 6, b="hello", a=None)

mais vous devez vous rappeler le MRO, c'est vraiment déroutant.

Je peux être un peu ennuyeux, mais j'ai remarqué que les gens oubliaient chaque fois d'utiliser *args et **kwargs quand ils surchargent une méthode, alors que c'est l'une des rares utilisations vraiment utiles et saines de ces 'variables magiques'.


13
2018-04-21 21:33



Un autre point non encore couvert est le passage des paramètres pour l'initialisation des classes. Depuis la destination de super dépend de la sous-classe, le seul moyen de transmettre les paramètres est de les emballer tous ensemble. Veillez ensuite à ne pas avoir le même nom de paramètre avec des significations différentes.

Exemple:

class A(object):
    def __init__(self, **kwargs):
        print('A.__init__')
        super().__init__()

class B(A):
    def __init__(self, **kwargs):
        print('B.__init__ {}'.format(kwargs['x']))
        super().__init__(**kwargs)


class C(A):
    def __init__(self, **kwargs):
        print('C.__init__ with {}, {}'.format(kwargs['a'], kwargs['b']))
        super().__init__(**kwargs)


class D(B, C): # MRO=D, B, C, A
    def __init__(self):
        print('D.__init__')
        super().__init__(a=1, b=2, x=3)

print(D.mro())
D()

donne:

[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
D.__init__
B.__init__ 3
C.__init__ with 1, 2
A.__init__

Appel de la super classe __init__ directement à l'affectation plus directe des paramètres est tentant mais échoue s'il y a super appel dans une super classe et / ou le MRO est changé et la classe A peut être appelée plusieurs fois, en fonction de l'implémentation.

Pour conclure: l'héritage coopératif et les paramètres super et spécifiques pour l'initialisation ne fonctionnent pas très bien ensemble.


10
2017-07-29 11:57



Global

En supposant que tout descend de object (vous êtes seul si ce n'est pas le cas), Python calcule un ordre de résolution de méthode (MRO) basé sur votre arbre d'héritage de classe. Le MRO satisfait 3 propriétés:

  • Les enfants d'une classe passent devant leurs parents
  • Les parents de gauche viennent avant les bons parents
  • Une classe n'apparaît qu'une fois dans la MRO

Si une telle commande n'existe pas, les erreurs Python. Le fonctionnement interne de ceci est une Linéarisation C3 de l'ascendance des classes. Lisez tout a propos de ça ici: https://www.python.org/download/releases/2.3/mro/

Ainsi, dans les deux exemples ci-dessous, c'est:

  1. Enfant
  2. La gauche
  3. Droite
  4. Parent

Lorsqu'une méthode est appelée, la première occurrence de cette méthode dans le MRO est celle qui est appelée. Toute classe qui n'implémente pas cette méthode est ignorée. Tout appel à super dans cette méthode appelera l'occurrence suivante de cette méthode dans le MRO. Par conséquent, il importe à la fois quel ordre vous placez les classes dans l'héritage, et où vous mettez les appels à super dans les méthodes.

Avec super premier de chaque méthode

class Parent(object):
    def __init__(self):
        super(Parent, self).__init__()
        print "parent"

class Left(Parent):
    def __init__(self):
        super(Left, self).__init__()
        print "left"

class Right(Parent):
    def __init__(self):
        super(Right, self).__init__()
        print "right"

class Child(Left, Right):
    def __init__(self):
        super(Child, self).__init__()
        print "child"

Child() Les sorties:

parent
right
left
child

Avec super dernier dans chaque méthode

class Parent(object):
    def __init__(self):
        print "parent"
        super(Parent, self).__init__()

class Left(Parent):
    def __init__(self):
        print "left"
        super(Left, self).__init__()

class Right(Parent):
    def __init__(self):
        print "right"
        super(Right, self).__init__()

class Child(Left, Right):
    def __init__(self):
        print "child"
        super(Child, self).__init__()

Child() Les sorties:

child
left
right
parent

9
2017-09-18 18:44