web-dev-qa-db-fra.com

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

Je suis assez nouveau dans la programmation orientée objet Python et j'ai du mal à comprendre la fonction super() (nouvelles classes de style), en particulier en ce qui concerne 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 la classe Third() héritera des deux méthodes du constructeur. Si oui, lequel sera utilisé avec super () et pourquoi?

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

746
Callisto

Ceci est détaillé avec une quantité raisonnable de détails par Guido lui-même dans son article de blog Ordre de résolution de méthode (incluant 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 répertoriés de gauche à droite. Dans ce cas, nous cherchons __init__. Donc, si vous définissez

class Third(First, Second):
    ...

Python commencera par examiner First et, si First ne possède pas l'attribut, il examinera ensuite Second.

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

Donc, 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].

Soit dit en passant: si Python ne peut pas trouver un ordre de résolution de méthode cohérent, il lève une exception au lieu de revenir à un comportement qui pourrait surprendre l'utilisateur.

Édité pour ajouter un exemple de 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"

Le MRO de Third devrait-il être [First, Second] ou [Second, First]? Il n'y a aucune 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

Edit: Je vois plusieurs personnes affirmer que les exemples ci-dessus manquent d'appels super(), alors laissez-moi vous expliquer: le but de ces exemples est de montrer comment se trouve le MRO construit. Ils ne sont pas destinés à imprimer "première\nseconde\troisième" ou autre chose. Vous pouvez - et, bien sûr, jouer avec l'exemple, ajouter des appels super(), voir ce qui se passe et mieux comprendre le 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'>)
615
rbp

Votre code et les autres réponses sont tous bogués. Il manque les appels super() dans les deux premières classes requises pour que les sous-classes coopératives fonctionnent.

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 la MRO à chaque étape. C'est pourquoi First et Second doivent également l'avoir, sinon l'exécution s'arrête à la fin de Second.__init__().

Voici ce que je reçois:

>>> Third()
second
first
third
219
lifeless

Je voulais élaborer la réponse est sans vie un peu parce que, lorsque j'ai commencé à lire sur l'utilisation de super () dans une hiérarchie d'héritage multiple en Python, je ne l'ai pas compris immédiatement.

Ce que vous devez comprendre, c’est que super(MyClass, self).__init__() fournit la méthode next __init__ conformément à l’algorithme MRO (Method Resolution Ordering) utilisé dans le contexte de l’héritage complet. hiérarchie.

Cette dernière partie est cruciale à comprendre. Reprenons 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 Method Order Order de Guido van Rossum, l'ordre de résolution de __init__ est calculé (avant Python 2.3) à l'aide d'une "traversée de la profondeur de gauche à droite":

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

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

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

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

  1. Selon MRO, __init__ of Third est appelé en premier.

  2. Ensuite, selon la MRO, dans la méthode __init__, super(Third, self).__init__() se résout en la méthode __init__ de First, qui est appelée.

  3. Dans __init__ of First super(First, self).__init__() appelle le __init__ of Deuxièmement, car c’est ce que le MRO dicte!

  4. Dans __init__ de Second super(Second, self).__init__() appelle le __init__ de l'objet, ce qui équivaut à rien. Après cela , "seconde" est imprimé .

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

  6. Une fois que super(Third, self).__init__() est terminé, "Ça y est" est imprimé .

Ceci explique en détail 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 et fonctionne bien dans les cas complexes, mais j’imagine que l’utilisation du "parcours de profondeur de gauche à droite" + "en supprimant les 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!

155
Visionscaper

Cela s'appelle 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.

49
monoceres

Voici comment j'ai résolu d'avoir plusieurs héritages avec différentes variables pour l'initialisation et plusieurs MixIns avec le même appel de fonction. J'ai dû explicitement ajouter des variables à ** kwargs passé et ajouter une interface MixIn pour être un noeud final pour les super appels.

Ici Aest une classe de base extensible et Bet Csont des classes MixIn qui fournissent la fonction fname__. Aet Battendent tous deux le paramètre vdans leurs __init__ et Cattend wname__. La fonction fprend un paramètre yname__. Qhérite des trois classes. MixInFest l'interface de mixin pour Bet Cname__.


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)
25
brent.payne

Je comprends que cela ne réponde pas directement à la question super(), mais j'estime qu'il est suffisamment pertinent pour le 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 simplement que si vous le faites de cette façon, vous devrez les appeler manuellement car je suis presque sûr que First '__init__() ne sera pas appelé.

19
Seaux

Global

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

Si aucun ordre de ce type n'existe, les erreurs Python. Le fonctionnement interne de ceci est une linéarisation C3 de l'ascendance des classes. Lisez tout à ce sujet ici: https://www.python.org/download/releases/2.3/mro/

Ainsi, dans les deux exemples ci-dessous, il s’agit de:

  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 au sein de cette méthode appellera la prochaine occurrence de cette méthode dans le MRO. Par conséquent, l'ordre dans lequel vous placez les classes en héritage est important, de même que l'emplacement où vous placez les appels à super dans les méthodes.

Avec super en 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() Outputs:

parent
right
left
child

Avec super last 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() Outputs:

child
left
right
parent
18
Zags

À propos de commentaire de @ calfzho , vous pouvez utiliser, comme d'habitude, **kwargs:

Exemple 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 énervant, mais j’ai remarqué que les gens oubliaient chaque fois d’utiliser *args et **kwargs lorsqu’ils surchargent une méthode, alors qu’il s’agit de l’une des rares utilisations réellement utiles et rationnelles de ces "variables magiques".

15
Marco Sulla

Un autre point non encore couvert est la transmission des paramètres pour l’initialisation des classes. Puisque la destination de super dépend de la sous-classe, le seul bon moyen de transmettre des paramètres consiste à les regrouper. Ensuite, faites 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__

L'appel de la super classe __init__ directement à une affectation plus directe des paramètres est tentant, mais échoue s'il existe un appel super dans une super classe et/ou si la MRO est modifiée et que 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.

11
Trilarion
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()

La sortie est

first 10
second 20
that's it

Call to Third () appelle le init défini dans Third. Et l'appel à super dans cette routine appelle init défini dans First. MRO = [Premier, Deuxième]. Maintenant, l'appel à super dans init défini dans First continue à chercher MRO et trouve init défini dans Second, et tout appel à super frappe sur l'objet par défaut init. J'espère que cet exemple clarifie le concept.

Si vous n'appelez pas super de premier. La chaîne s'arrête et vous obtiendrez la sortie suivante.

first 10
that's it
4
Seraj Ahmad

Dans learningpythonthehardway, j'apprends quelque chose appelée super (), une fonction intrinsèque si je ne me trompe pas. L'appel de la fonction super () peut aider l'héritage à passer par le parent et les "frères et sœurs" et vous aide à mieux voir. Je suis toujours débutant, mais j'aime partager mon expérience d'utilisation de ce super () en python2.7.

Si vous avez lu les commentaires de cette page, vous entendrez parler de la résolution de méthode (MRO), la méthode étant la fonction que vous avez écrite. MRO utilisera le schéma Profondeur-premier-gauche-à-droite pour rechercher et exécuter. Vous pouvez faire plus de recherche à ce sujet.

En ajoutant la fonction super ()

super(First, self).__init__() #example for class First.

Vous pouvez connecter plusieurs instances et "familles" avec super (), en ajoutant chacune d'entre elles. Et il va exécuter les méthodes, les passer en revue et s'assurer que vous ne les avez pas manquées! Cependant, en les ajoutant avant ou après fait une différence, vous saurez si vous avez suivi l’apprentissage de l’exercice 44. Laissez le plaisir commencer!

En prenant l'exemple ci-dessous, vous pouvez copier et coller et essayer de l'exécuter:

class First(object):
    def __init__(self):

        print("first")

class Second(First):
    def __init__(self):
        print("second (before)")
        super(Second, self).__init__()
        print("second (after)")

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


class Fourth(First):
    def __init__(self):
        print("fourth (before)")
        super(Fourth, self).__init__()
        print("fourth (after)")


class Fifth(Second, Third, Fourth):
    def __init__(self):
        print("fifth (before)")
        super(Fifth, self).__init__()
        print("fifth (after)")

Fifth()

Comment ça marche? L'instance de fifth () ira comme ceci. Chaque étape va de classe en classe où la super fonction a été ajoutée.

1.) print("fifth (before)")
2.) super()>[Second, Third, Fourth] (Left to right)
3.) print("second (before)")
4.) super()> First (First is the Parent which inherit from object)

Le parent a été retrouvé et il ira continuer aux troisième et quatrième !!

5.) print("third (before)")
6.) super()> First (Parent class)
7.) print ("Fourth (before)")
8.) super()> First (Parent class)

Maintenant, toutes les classes avec super () ont été consultées! La classe parente a été trouvée et exécutée et elle continue maintenant à décompresser la fonction dans les héritages pour achever les codes.

9.) print("first") (Parent)
10.) print ("Fourth (after)") (Class Fourth un-box)
11.) print("third (after)") (Class Third un-box)
12.) print("second (after)") (Class Second un-box)
13.) print("fifth (after)") (Class Fifth un-box)
14.) Fifth() executed

Les résultats du programme ci-dessus:

fifth (before)
second (before
third (before)
fourth (before)
first
fourth (after)
third (after)
second (after)
fifth (after)

Pour moi, ajouter super () me permet de voir plus clairement comment python exécuterait mon codage et de m'assurer que l'héritage puisse accéder à la méthode que je voulais.

3
Will MeetYou

J'aimerais ajouter à ce que @Visionscaper dit en haut:

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

Dans ce cas, l'interprète ne filtre pas la classe d'objet car elle est dupliquée, mais Second car apparaît en tête et n'apparaît pas à la fin dans un sous-ensemble de la hiérarchie. Bien que l'objet n'apparaisse que dans les positions de la queue, il n'est pas considéré comme une position forte dans l'algorithme C3 pour déterminer la priorité.

La linéarisation (mro) d’une classe C, L (C), est la

  • 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 est réalisée en sélectionnant les classes communes qui apparaissent en tête des listes et non en fin de liste, car l'ordre est important (cela deviendra clair ci-dessous).

La linéarisation de Third 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 implémentation super () 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 comment cette méthode sera résolue

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
supi

Peut-être qu'il reste encore quelque chose à ajouter, un petit exemple avec Django rest_framework et des décorateurs. Ceci fournit une réponse à la question implicite: "pourquoi voudrais-je cela de toute façon?"

Comme nous l’avons dit: nous sommes avec Django rest_framework et nous utilisons des vues génériques. Pour chaque type d’objets de notre base de données, nous nous retrouvons avec une classe de vues fournissant GET et POST pour des listes d’objets, et une autre classe de vues fournissant GET, PUT et DELETE pour des objets individuels.

Maintenant, le POST, le PUT et le DELETE que nous voulons décorer avec le login_ango requis de Django. Notez que cela touche les deux classes, mais pas toutes les méthodes de l'une ou l'autre classe.

Une solution pourrait passer par plusieurs héritages.

from Django.utils.decorators import method_decorator
from Django.contrib.auth.decorators import login_required

class LoginToPost:
    @method_decorator(login_required)
    def post(self, arg, *args, **kwargs):
        super().post(arg, *args, **kwargs)

De même pour les autres méthodes.

Dans la liste d'héritage de mes classes concrètes, j'ajouterais mon LoginToPostavant ListCreateAPIViewet LoginToPutOrDeleteavant RetrieveUpdateDestroyAPIViewname__. Mes classes concrètes 'getresteraient non décorées.

1
mariotomo