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

je suis assez nouveau en programmation orientée objet Python et j'ai des problèmes comprendre la fonction super() (nouvelles classes de style) surtout lorsqu'il s'agit d'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: la classe Third() héritera-t-elle des deux méthodes de constructeur? Si oui, alors qui sera exécuté avec super() et pourquoi?

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

640
demandé sur Peter O. 2010-07-19 01:40:25

11 réponses

Ceci est détaillé avec une quantité raisonnable de détails par Guido lui-même dans son billet de blog Method Resolution Order (y compris deux tentatives antérieures).

dans votre exemple, Third() appellera First.__init__ . Python cherche 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 commencera par regarder First , et si First n'a pas l'attribut, puis il va chercher à Second .

cette situation devient plus complexe lorsque l'héritage commence à se croiser (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 sur la liste d'héritage, en commençant par la classe enfant elle-même.

donc, pour 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].

par ailleurs: si Python ne peut pas trouver un ordre de résolution de méthode cohérent, il soulèvera une exception, au lieu de retomber dans un comportement qui pourrait surprendre l'utilisateur.

édité pour ajouter l'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 être [First, Second] ou [Second, First] ? Il n'est pas évident attente, et Python va soulever une erreur:

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

Edit: je vois plusieurs personnes faisant valoir que les exemples ci-dessus manquent super() appels, laissez-moi donc expliquer: le point des exemples est de montrer comment le MRO est construit. Ils sont pas destiné à imprimer" premier\nsecond\third " ou autre. Vous pouvez – et, bien sûr, jouer avec l'exemple, ajouter super() des appels, voir ce qui se passe, et d'acquérir une compréhension approfondie du modèle d'héritage de Python. Mais mon but 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'>)
530
répondu rbp 2017-11-14 07:21:21

votre code, et les autres réponses, sont tout buggy. Il leur manque les appels super() dans les deux premières classes qui sont nécessaires pour que le sous-classement coopératif fonctionne.

Voici une version fixe 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")

l'appel super() trouve la méthode suivante dans le MRO à chaque étape, c'est pourquoi le premier et le Second doivent l'avoir aussi, sinon l'exécution s'arrête à la fin de Second.__init__() .

This c'est ce que j'obtiens:

>>> Third()
second
first
third
187
répondu lifeless 2017-12-09 01:33:44

je voulais élaborer la réponse par lifeless un peu parce que quand j'ai commencé à lire sur la façon d'utiliser super() dans une hiérarchie d'héritage multiple en Python, je ne l'ai pas obtenu immédiatement.

ce que vous devez comprendre est que super(MyClass, self).__init__() fournit le suivant __init__ méthode selon la méthode utilisée ordonnant la méthode (MRO) algorithme dans le contexte de l'héritage complet hiérarchie .

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

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 de méthode par Guido van Rossum, l'ordre de résolution __init__ est calculé (avant Python 2.3) en utilisant une "profondeur-première traversée de gauche à droite":

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

après suppression de tous les doublons, sauf le dernier, nous obtenez de l' :

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

donc, suivons ce qui se passe quand nous instancions une instance de la classe Third , par exemple x = Third() .

  1. selon MRO __init__ de troisième est appelé en premier.

  2. ensuite, selon le MRO, à l'intérieur de la __init__ méthode super(Third, self).__init__() se résout à la __init__ méthode de premier, qui est appelée.

  3. à l'Intérieur __init__ de Première super(First, self).__init__() appelle la __init__ de en Second lieu, parce que c'est ce que le MRO diktats!

  4. à l'Intérieur __init__ de Deuxième super(Second, self).__init__() appels le __init__ de l'objet, qui ne représente rien. Après que "second" est imprimé .

  5. après super(First, self).__init__() complété, "premier" est imprimé .

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

Cela en détail pourquoi l'instanciation de Troisième() des résultats pour :

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

l'algorithme MRO a été amélioré à partir de Python 2.3 Pour bien fonctionner dans les cas complexes, mais je suppose que l'utilisation de la " profondeur-d'abord de gauche à droite traversal " + "supprimer les doublons expect for the last" fonctionne toujours dans la plupart des cas (veuillez commenter si ce n'est pas le cas). N'oubliez pas de lire le billet du blog de Guido!

131
répondu Visionscaper 2017-05-23 11:47:30

ceci est connu comme le Diamond Problem , la page a une entrée sur Python, mais en bref, Python appellera les méthodes de la superclasse de gauche à droite.

46
répondu monoceres 2010-08-02 09:52:10

C'est à la façon dont j'ai résolu à la question d'avoir l'héritage multiple avec des variables différentes pour l'initialisation et d'avoir des MixIns multiples avec le même appel de fonction. J'ai dû ajouter explicitement des variables à passed * * kwargs et ajouter une interface MixIn pour être un endpoint pour les super appels.

ici A est une classe de base extensible et B et C sont des classes de MixIn tous les deux qui fournissent la fonction f . A et B les deux attendent le paramètre v dans leur __init__ et C attend w . La fonction f prend un paramètre y . Q hérite des trois classes. MixInF est l'interface de 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)
23
répondu brent.payne 2014-08-17 19:22:31

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

il y a aussi une façon 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)

il suffit de noter que si vous le faites de cette façon, vous devrez appeler chacun manuellement car je suis assez sûr que First 's __init__() ne sera pas appelé.

16
répondu Seaux 2017-12-09 01:31:00

à propos de le commentaire de @calfzhou , vous pouvez utiliser, comme d'habitude, **kwargs :

exemple de fonctionnement 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 positionnellement:

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

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

je peux être un peu agaçant, mais j'ai remarqué que les gens ont oublié chaque fois pour utiliser *args et **kwargs quand ils outrepassent une méthode, alors que c'est l'un des rares vraiment utile et saine utilisation de ces 'variables magiques'.

14
répondu Marco Sulla 2016-04-21 21:33:32

un autre point non encore couvert est les paramètres d'initialisation des classes. Depuis la destination de super dépend de la sous-classe la seule bonne façon de passer les paramètres d'emballage tous ensemble. Alors attention à 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__

appelant la classe super __init__ directement à plus directe l'assignation des paramètres est tentante mais échoue s'il y a un appel super dans une super classe et/ou le MRO est modifié et la classe a peut être appelée plusieurs fois, selon 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.

11
répondu Trilarion 2014-07-29 11:57:13

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 de venir avant que leurs parents
  • les parents de gauche viennent avant les parents de droite
  • d'Une classe n'apparaît qu'une fois dans le MRO

si un tel ordre n'existe pas, des erreurs Python. Le fonctionnement interne de ceci est une linéarisation C3 de l'ascendance des classes. Tout savoir ici: https://www.python.org/download/releases/2.3/mro /

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

  1. enfant
  2. à gauche
  3. Droit
  4. Parent

Lorsqu'une méthode est appelée, la première occurrence de cette méthode dans le MRO est celui qui est appelé. Toute classe qui implémente cette méthode est ignorée. Tout appel à super dans le cadre de cette méthode appellera la prochaine occurrence de cette méthode dans le BM. 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 dans 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() 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() sorties:

child
left
right
parent
10
répondu Zags 2017-12-13 20:35:43
class First(object):
  def __init__(self, a):
    print "first", a
    super(First, self).__init__(20)

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

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

t = Third()

sortie est

first 10
second 20
that's it

Call to Third () localise le init défini dans Third. Et appel à super dans cette routine invoque init défini en premier. MRO=[First, Second]. Maintenant appel à super dans init défini dans la première va continuer la recherche MRO et trouver init défini dans la seconde, et tout appel à super va frapper l'objet par défaut init . J'espère que cet exemple clarifie le concept.

si vous n'appelez pas super en premier. La chaîne s'arrête et vous obtiendrez le résultat suivant.

first 10
that's it
4
répondu Seraj Ahmad 2016-11-21 02:23:34

je voudrais ajouter à ce que @Visionscaper dit en haut:

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

dans ce cas, l'interpréteur ne filtre pas la classe de l'objet parce que celle-ci est dupliquée, mais plutôt parce que la seconde apparaît en position de tête et n'apparaît pas en position de queue dans un sous-ensemble de la hiérarchie. Alors que l'objet apparaît seulement dans les positions de queue et n'est pas considéré comme une position forte dans L'algorithme C3 pour déterminer la priorité.

le la linéarisation(mro) d'une Classe C, L (C), est le

  • la Classe C
  • plus la fusion de
    • linéarisation de ses parents P1, P2, .. = L (P1, P2,...) et
    • la liste de ses parents P1, P2, ..

la fusion linéarisée se fait en sélectionnant les classes communes qui apparaissent en tête de liste et non en queue depuis l'ordre les questions(deviendra clair ci-dessous)

la linéarisation du tiers peut être calculée comme suit:

    L(O)  := [O]  // the linearization(mro) of O(object), because O has no parents

    L(First)  :=  [First] + merge(L(O), [O])
               =  [First] + merge([O], [O])
               =  [First, O]

    // Similarly, 
    L(Second)  := [Second, O]

    L(Third)   := [Third] + merge(L(First), L(Second), [First, Second])
                = [Third] + merge([First, O], [Second, O], [First, Second])
// class First is a good candidate for the first merge step, because it only appears as the head of the first and last lists
// class O is not a good candidate for the next merge step, because it also appears in the tails of list 1 and 2, 
                = [Third, First] + merge([O], [Second, O], [Second])
// class Second is a good candidate for the second merge step, because it appears as the head of the list 2 and 3
                = [Third, First, Second] + merge([O], [O])            
                = [Third, First, Second, O]

donc pour une super() implémentation dans le code suivant:

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"

, il devient évident de savoir comment cette méthode sera résolu

Third.__init__() ---> First.__init__() ---> Second.__init__() ---> 
Object.__init__() ---> returns ---> Second.__init__() -
prints "second" - returns ---> First.__init__() -
prints "first" - returns ---> Third.__init__() - prints "that's it"
2
répondu supi 2018-06-10 11:16:15