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


889

Je suis à peu près nouveau dans la programmation orientée objet Python et j'ai du mal à comprendre la super()fonction (nouvelles classes de style), surtout 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: la Third()classe héritera-t-elle des deux méthodes constructeurs? Si oui, alors lequel sera exécuté 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 de la méthode Python ( MRO ).


En fait, l'héritage multiple est le seul cas où il super()est utile. Je ne recommanderais pas de l'utiliser avec des classes utilisant l'héritage linéaire, où c'est juste une surcharge inutile.
Bachsau

9
@Bachsau est techniquement correct en ce qu'il s'agit d'une petite surcharge mais super () est plus pythonique et permet une refactorisation et des modifications du code au fil du temps. Utilisez super () sauf si vous avez vraiment besoin d'une méthode spécifique à une classe nommée.
Paul Whipp

2
Un autre problème avec super(), c'est qu'il oblige chaque sous-classe à l'utiliser également, tandis que lorsqu'il ne l'utilise pas super(), tout le monde sous-classe peut décider lui-même. Si un développeur l'utilisant ne sait pas super()ou ne sait pas qu'il a été utilisé, des problèmes avec la macro peuvent survenir et sont très difficiles à localiser.
Bachsau

J'ai trouvé pratiquement chaque réponse ici confuse d'une manière ou d'une autre. Vous feriez plutôt référence ici ici .
matanster

Réponses:


708

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

Dans votre exemple, Third()va appeler 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 recherchons __init__. Donc, si vous définissez

class Third(First, Second):
    ...

Python commencera par regarder First, et, s'il Firstn'a pas l'attribut, alors il regardera Second.

Cette situation devient plus complexe lorsque l'héritage commence à croiser les chemins (par exemple s'il est Firsthérité de Second). Lisez le lien ci-dessus pour plus de détails, mais, en un mot, Python essaiera 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].

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

Modifié 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"

Devrait Thirdêtre MRO » [First, Second]ou [Second, First]? Il n'y a aucune attente évidente et Python générera 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' super()appels, alors laissez-moi vous expliquer: le but des exemples est de montrer comment le MRO est construit. Ils ne sont pas destinés à imprimer "premier \ nsecond \ tiers" ou autre. Vous pouvez - et devriez, bien sûr, jouer avec l'exemple, ajouter des super()appels, voir ce qui se passe et acquérir une compréhension plus approfondie 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'>)

12
Cela devient plus intéressant (et, sans doute, plus déroutant) lorsque vous commencez à appeler super () dans First, Second et Third [ pastebin.com/ezTyZ5Wa ].
gatoatigrado

52
Je pense que le manque de super appels dans les premières classes est un très gros problème avec cette réponse; sans discuter comment / pourquoi cette compréhension critique importante de la question est perdue.
Sam Hartman

3
Cette réponse est tout simplement fausse. Sans super () appels chez les parents, rien ne se passera. La réponse de @ lifeless est la bonne.
Cerin

8
@Cerin Le but de cet exemple est de montrer comment le MRO est construit. L'exemple n'est PAS destiné à imprimer "premier \ nsecond \ tiers" ou autre. Et le MRO est en effet correct: Quatrième .__ mro__ == (<class ' main .Fourth'>, <class ' main .Second'>, <class ' main .Tthird'>, <class ' main .First'>, < type 'objet'>)
rbp

2
Pour autant que je puisse voir, il manque à cette réponse une des questions d'OP, qui est "Et si vous voulez exécuter l'autre?". J'aimerais voir la réponse à cette question. Sommes-nous simplement censés nommer explicitement la classe de base?
Ray

252

Votre code et les autres réponses sont tous bogués. Ils manquent les super()appels 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' super()appel trouve la méthode suivante dans le MRO à chaque étape, c'est pourquoi First et Second doivent l'avoir aussi, sinon l'exécution s'arrête à la fin de Second.__init__().

Voici ce que j'obtiens:

>>> Third()
second
first
third

90
Que faire si ces classes ont besoin de paramètres différents pour s'initialiser?
calfzhou

2
"sous-classement coopératif"
Quant Metropolis

6
De cette façon, les méthodes d' initialisation des DEUX classes de base seront exécutées, tandis que l'exemple d'origine appelle uniquement le premier init rencontré dans le MRO. Je suppose que cela est impliqué par le terme "sous-classement coopératif", mais une clarification aurait été utile ('Explicit vaut mieux qu'implicite', vous savez;))
Quant Metropolis

1
Oui, si vous transmettez différents paramètres à une méthode appelée via super, toutes les implémentations de cette méthode remontant le MRO vers object () doivent avoir des signatures compatibles. Cela peut être réalisé grâce aux paramètres des mots clés: acceptez plus de paramètres que la méthode n'en utilise et ignorez les autres. Il est généralement considéré comme laid de le faire, et dans la plupart des cas, l'ajout de nouvelles méthodes est préférable, mais init est (presque?) Unique en tant que nom de méthode spécial mais avec des paramètres définis par l'utilisateur.
sans vie

15
La conception de l' héritage multiple est vraiment très mauvaise en python. Les classes de base ont presque besoin de savoir qui va le dériver, et combien d'autres classes de base dériveront, et dans quel ordre ... sinon, superelles ne fonctionneront pas (en raison de la non-concordance des paramètres), ou elles n'appelleront pas peu de bases (parce que vous n'avez pas écrit superdans l'une des bases qui rompt le lien)!
Nawaz

186

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

Ce que vous devez comprendre, c'est qu'il super(MyClass, self).__init__()fournit la méthode suivante en __init__ fonction de 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. Reprenons l'exemple:

#!/usr/bin/env python2

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

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

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

Selon cet article sur Guido van Rossum sur l'ordre de résolution des méthodes , l'ordre à résoudre __init__est calculé (avant Python 2.3) à l'aide d'un "parcours de profondeur en premier de gauche à droite":

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

Après avoir supprimé tous les doublons, à l'exception du dernier, nous obtenons:

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

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

  1. Selon MRO Third.__init__s'exécute.
    • impressions Third(): entering
    • puis super(Third, self).__init__()s'exécute et MRO retourne First.__init__qui est appelé.
  2. First.__init__ s'exécute.
    • impressions First(): entering
    • puis super(First, self).__init__()s'exécute et MRO retourne Second.__init__qui est appelé.
  3. Second.__init__ s'exécute.
    • impressions Second(): entering
    • puis super(Second, self).__init__()s'exécute et MRO retourne object.__init__qui est appelé.
  4. object.__init__ s'exécute (pas d'instructions d'impression dans le code)
  5. l'exécution remonte à Second.__init__laquelle imprime ensuiteSecond(): exiting
  6. l'exécution remonte à First.__init__laquelle imprime ensuiteFirst(): exiting
  7. l'exécution remonte à Third.__init__laquelle imprime ensuiteThird(): exiting

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

Third(): entering
First(): entering
Second(): entering
Second(): exiting
First(): exiting
Third(): exiting

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 "traversée de profondeur en premier de gauche à droite" + "en supprimant les doublons attendus pour le dernier" fonctionne toujours dans la plupart des cas (veuillez si ce n'est pas le cas). N'oubliez pas de lire le blog de Guido!


6
Je ne comprends toujours pas pourquoi: Inside init of First super (First, self) .__ init __ () appelle l' init of Second, car c'est ce que le MRO dicte!
user389955

@ user389955 L'objet créé est de type Third qui a toutes les méthodes init. Donc, si vous supposez que MRO crée une liste de toutes les fonctions init dans un ordre spécifique, avec chaque super appel, vous avancez d'un pas jusqu'à la fin.
Sreekumar R

15
Je pense que l'étape 3 a besoin de plus d'explications: si elle Thirdn'a pas hérité de Second, puis super(First, self).__init__appeler object.__init__et après le retour, "premier" serait imprimé. Mais parce que Thirdhérite des deux Firstet Second, plutôt que d'appeler object.__init__après First.__init__le MRO, seul l'appel final object.__init__est conservé et les instructions print Firstet Secondne sont pas atteintes avant le object.__init__retour. Depuis qu'il a Secondété le dernier à appeler object.__init__, il revient à l'intérieur Secondavant de rentrer First.
MountainDrew

1
Fait intéressant, PyCharm semble savoir tout cela (ses conseils parlent des paramètres qui vont avec quels appels à super. Il a également une certaine notion de covariance des entrées, donc il reconnaît List[subclass]comme un List[superclass]si subclassest une sous-classe de superclass( Listvient du typingmodule de PEP 483 iirc).
Cabine Reb

Joli message mais je manque des informations sur les arguments des constructeurs, c'est-à-dire que se passe-t-il si Second et First attendent des arguments distincts? Le constructeur de First devra traiter certains des arguments et transmettre le reste à Second. Est-ce correct? Il ne me semble pas correct que First ait besoin de connaître les arguments requis pour Second.
Christian K.

58

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


Ce n'est pas le problème du diamant. Le problème du diamant comprend quatre classes et la question du PO n'en implique que trois.
Ian Goodfellow

147
objectest le quatrième
GP89

28

Voici comment j'ai résolu le problème d'avoir plusieurs héritages avec différentes variables pour l'initialisation et d'avoir plusieurs MixIns 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.

Voici Aune classe de base extensible Bet ce Csont des classes MixIn qui fournissent toutes deux une fonction f. Aet les Bdeux attendent un paramètre vdans leur __init__et Cattend w. La fonction fprend un paramètre y. Qhérite des trois classes. MixInFest l'interface de mixage pour Bet 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)

Je pense que cela devrait peut-être être une question-réponse distincte, car le MRO est un sujet suffisamment vaste à lui seul sans aborder différents arguments entre les fonctions avec héritage (l'héritage multiple est un cas spécial).
sans vie

8
Théoriquement, oui. Pratiquement, ce scénario se répète chaque fois que je rencontre l'héritage Diamond en python, je l'ai donc ajouté ici. Depuis, c'est là que je vais chaque fois que je ne peux pas éviter proprement l'héritage des diamants. Voici quelques liens supplémentaires pour moi à l'avenir: rhettinger.wordpress.com/2011/05/26/super-considered-super code.activestate.com/recipes/…
brent.payne

Ce que nous voulons, ce sont des programmes avec des noms de paramètres sémantiquement significatifs. Mais dans cet exemple, presque tous les paramètres sont nommés de manière anonyme, ce qui rendra beaucoup plus difficile pour le programmeur d'origine de documenter le code et pour un autre programmeur de lire le code.
Arthur

Une demande de tirage au dépôt github avec des noms descriptifs serait appréciée
brent.payne

@ brent.payne Je pense que @Arthur signifie que toute votre approche repose sur l'utilisation de args/ kwargsplutôt que sur des paramètres nommés.
max

25

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

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)

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


5
Elle ne sera pas appelée car vous n'avez pas appelé chaque classe héritée. Le problème est plutôt que si Firstet Secondhéritent tous les deux d'une autre classe et l'appellent directement, cette classe commune (point de départ du losange) est appelée deux fois. super évite cela.
Trilarion

@Trilarion Oui, j'étais sûr que non. Cependant, je ne savais pas définitivement et je ne voulais pas dire comme si je le savais, même si c'était très peu probable. C'est un bon point sur le fait d' objectêtre appelé deux fois. Je n'y ai pas pensé. Je voulais juste souligner que vous appelez directement les classes parentes.
Seaux

Malheureusement, cela casse si init essaie d'accéder à des méthodes privées :(
Erik Aronesty

22

Global

En supposant que tout découle 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 avant leurs parents
  • Les parents de gauche passent avant les bons parents
  • Une classe n'apparaît qu'une seule fois dans le MRO

Si aucun ordre de ce type n'existe, des 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, 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 à l' superintérieur de cette méthode appellera la prochaine occurrence de cette méthode dans le MRO. Par conséquent, il importe à la fois dans quel ordre vous placez les classes en héritage et où vous placez les appels àsuper dans les méthodes.

Avec le superpremier 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() Les sorties:

parent
right
left
child

Avec le superdernier 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

Je vois que vous pouvez accéder à l' Leftaide super()de Child. supposons que je veuille y accéder Rightde l'intérieur Child. Existe-t-il un moyen d'accéder Rightà Childpartir de Super? Ou dois-je appeler directement Rightde l'intérieur super?
alpha_989

4
@ alpha_989 Si vous souhaitez accéder uniquement à la méthode d'une classe particulière, vous devez référencer cette classe directement plutôt que d'utiliser super. Super consiste à suivre la chaîne d'héritage, sans arriver à la méthode d'une classe spécifique.
Zags

1
Merci d'avoir mentionné explicitement «Une classe n'apparaît qu'une seule fois dans le MRO». Cela a résolu mon problème. Maintenant, je comprends enfin comment fonctionne l'héritage multiple. Il fallait que quelqu'un mentionne les propriétés du MRO!
Tushar Vazirani

18

À propos du 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 de manière positionnelle:

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 oublient à chaque fois d'utiliser *argset **kwargsquand ils remplacent une méthode, alors que c'est l'une des rares utilisations vraiment utiles et sensées de ces `` variables magiques ''.


Wow c'est vraiment moche. C'est dommage que vous ne puissiez pas simplement dire quelle superclasse spécifique vous voulez appeler. Pourtant, cela me donne encore plus d'incitation à utiliser la composition et à éviter les héritages multiples comme la peste.
Tom Busby

15

Un autre point non encore couvert est le passage de paramètres pour l'initialisation des classes. Depuis la destination desuper dépend de la sous-classe, le seul bon moyen de passer les paramètres est de les regrouper tous. 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__

Appeler __init__directement la super classe pour une affectation plus directe des paramètres est tentant mais échoue s'il y en asuper appel dans une super classe et / ou le MRO est changé 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.


5
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

L'appel à Third () localise l' init défini dans Third. Et appelez super dans cette routine invoque init défini dans First. MRO = [Premier, deuxième]. Maintenant, l'appel à super dans init défini dans First continuera la recherche MRO et trouvera init défini dans Second, et tout appel à super frappera l'objet init par défaut . J'espère que cet exemple clarifie le concept.

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

first 10
that's it

1
c'est parce que dans la classe d'abord, vous avez appelé «imprimer» d'abord, puis «super».
rocky qi

2
c'était pour illustrer l'ordre d'appel
Seraj Ahmad

4

Dans learningpythonthehardway j'apprends quelque chose appelé super () une fonction intégrée si elle ne se 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 aider à voir plus clairement. Je suis encore débutant mais j'aime partager mon expérience sur l'utilisation de ce super () en python2.7.

Si vous avez lu les commentaires de cette page, vous entendrez parler de l'ordre de résolution de méthode (MRO), la méthode étant la fonction que vous avez écrite, MRO utilisera le schéma Profondeur en premier de gauche à droite pour rechercher et exécuter. Vous pouvez faire plus de recherches à 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'elles. Et il exécutera les méthodes, les passera en revue et s'assurera que vous ne les avez pas ratées! Cependant, les ajouter avant ou après fait une différence, vous saurez si vous avez fait l'exercice d'apprentissage 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 cinquième () va 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 () sont accessibles! La classe parente a été trouvée et exécutée et maintenant elle continue de décompresser la fonction dans les héritages pour terminer 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

Le résultat du programme ci-dessus:

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

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


Merci pour la démo détaillée!
Tushar Vazirani

3

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

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

Dans ce cas, l'interpréteur ne filtre pas la classe d'objets parce qu'elle est dupliquée, mais plutôt parce que Second apparaît dans une position de tête et n'apparaît pas dans la position de queue dans un sous-ensemble de hiérarchie. Alors que l'objet n'apparaît que dans les positions de queue et 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 se fait en sélectionnant les classes communes qui apparaissent en tête de liste et non en queue car l'ordre compte (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]

Ainsi 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"

"C'est plutôt parce que Second apparaît dans une position de tête et n'apparaît pas dans la position de queue dans un sous-ensemble de hiérarchie." Il n'est pas clair ce qu'est une position de tête ou de queue, ni ce qu'est un sous-ensemble de hiérarchie ou à quel sous-ensemble vous faites référence.
OrangeSherbet

La position de queue fait référence aux classes qui sont plus élevées dans la hiérarchie des classes et vice versa. La classe de base «objet» est à la fin de la queue. La clé pour comprendre l'algorithme mro est de savoir comment «Second» apparaît comme le super de «First». Nous supposerions normalement qu'il s'agit de la classe «objet». C'est vrai, mais seulement dans la perspective de la «première» classe. Cependant, vu du point de vue de la «troisième classe», l'ordre de hiérarchie pour le «premier» est différent et est calculé comme indiqué ci-dessus. l'algorithme mro essaie de créer cette perspective (ou sous-ensemble de hiérarchie) pour toutes les classes héritées multiples
supi

3

En python 3.5+, l'héritage semble prévisible et très agréable pour moi. Veuillez regarder ce code:

class Base(object):
  def foo(self):
    print("    Base(): entering")
    print("    Base(): exiting")


class First(Base):
  def foo(self):
    print("   First(): entering Will call Second now")
    super().foo()
    print("   First(): exiting")


class Second(Base):
  def foo(self):
    print("  Second(): entering")
    super().foo()
    print("  Second(): exiting")


class Third(First, Second):
  def foo(self):
    print(" Third(): entering")
    super().foo()
    print(" Third(): exiting")


class Fourth(Third):
  def foo(self):
    print("Fourth(): entering")
    super().foo()
    print("Fourth(): exiting")

Fourth().foo()
print(Fourth.__mro__)

Les sorties:

Fourth(): entering
 Third(): entering
   First(): entering Will call Second now
  Second(): entering
    Base(): entering
    Base(): exiting
  Second(): exiting
   First(): exiting
 Third(): exiting
Fourth(): exiting
(<class '__main__.Fourth'>, <class '__main__.Third'>, <class '__main__.First'>, <class '__main__.Second'>, <class '__main__.Base'>, <class 'object'>)

Comme vous pouvez le voir, il appelle foo exactement UNE fois pour chaque chaîne héritée dans le même ordre qu'il a été hérité. Vous pouvez obtenir cette commande en appelant . mro :

Quatrième -> Troisième -> Premier -> Deuxième -> Base -> objet


2

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

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

Maintenant, le POST, le PUT et le DELETE que nous voulons décorer avec le login_required de Django. Remarquez comment cela touche les deux classes, mais pas toutes les méthodes des deux classes.

Une solution pourrait passer par l'héritage multiple.

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 RetrieveUpdateDestroyAPIView. Mes cours concrets getresteraient sans décoration.


1

Publier cette réponse pour ma future référence.

L'héritage multiple Python doit utiliser un modèle en losange et la signature de la fonction ne doit pas changer dans le modèle.

    A
   / \
  B   C
   \ /
    D

L'exemple d'extrait de code serait: -

class A:
    def __init__(self, name=None):
        #  this is the head of the diamond, no need to call super() here
        self.name = name

class B(A):
    def __init__(self, param1='hello', **kwargs):
        super().__init__(**kwargs)
        self.param1 = param1

class C(A):
    def __init__(self, param2='bye', **kwargs):
        super().__init__(**kwargs)
        self.param2 = param2

class D(B, C):
    def __init__(self, works='fine', **kwargs):
        super().__init__(**kwargs)
        print(f"{works=}, {self.param1=}, {self.param2=}, {self.name=}")

d = D(name='Testing')

Ici, la classe A est object


1
Adevrait également appeler __init__. An'a pas "inventé" la méthode __init__, elle ne peut donc pas supposer qu'une autre classe peut avoir été utilisée Aplus tôt dans son MRO. La seule classe dont la __init__méthode n'appelle pas (et ne devrait pas) appeler super().__init__est object.
chepner

Ouais. C'est pourquoi j'ai écrit A est objectpeut - être je pense, je devrais écrire à la class A (object) : place
Akhil Nadh PC

Ane peut pas l'être objectsi vous ajoutez un paramètre à son __init__.
chepner
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.