En parlant desuper, tout le monde pourrait penser que c'est très simple — n'est-ce pas simplement utilisé pour appeler les méthodes de la classe parente ? Si c'était vraiment aussi simple, cet article n'existerait pas. Laissez-moi l'expliquer en détail.

Conventions

Avant de commencer, mettons-nous d'accord sur la version de Python utilisée dans cet article. Par défaut, nous utilisons Python 3, ce qui signifie que toutes les classes définies dans cet article sont des classes de nouveau style. Si vous utilisez Python 2, n'oubliez pas d'hériter deobject:
# 默认, Python 3
class A:
    pass

# Python 2
class A(object):
    pass

Une autre différence entre Python 3 et Python 2 est que Python 3 peut directement utilisersuper().xxxau lieu desuper(Class, self).xxx :

# 默认,Python 3
class B(A):
    def add(self, x):
        super().add(x)

# Python 2
class B(A):
    def add(self, x):
        super(B, self).add(x)

Donc, si vous utilisez Python 2, n'oubliez pas de remplacer lesuper()de l'article parsuepr(Class, self) 。

S'il existe d'autres cas incompatibles avec Python 2, je les signalerai dans l'article.

Héritage simple

Dans l'héritage simple,supercomme tout le monde le pense, super sert principalement à appeler les méthodes de la classe parente.

class A:
    def __init__(self):
        self.n = 2

    def add(self, m):
        print('self is {0} @A.add'.format(self))
        self.n += m


class B(A):
    def __init__(self):
        self.n = 3

    def add(self, m):
        print('self is {0} @B.add'.format(self))
        super().add(m)
        self.n += 3

Que pensez-vous que la valeur de b.nsera après l'exécution du code suivant ?

b = B()
b.add(2)
print(b.n)

Le résultat de l'exécution est le suivant :

self is <__main__.B object at 0x106c49b38> @B.add
self is <__main__.B object at 0x106c49b38> @A.add
8

Ce résultat illustre deux points :

  • 1, super().add(m)Il appelle bien la méthodeaddde la classe parente A.
  • 2, super().add(m)Lors de l'appel à la méthode de la classe parentedef add(self, m), à ce moment, dans la classe parente,selfn'est pas une instance de la classe parente mais une instance de la classe enfant, doncb.add(2)le résultat après est5plutôt que4 。

Je me demande si ce résultat est le même que celui que vous attendiez ? Voyons maintenant un exemple d'héritage multiple.

Héritage multiple

Cette fois, définissons une autreclass C, et uneclass D:

class C(A):
    def __init__(self):
        self.n = 4

    def add(self, m):
        print('self is {0} @C.add'.format(self))
        super().add(m)
        self.n += 4


class D(B, C):
    def __init__(self):
        self.n = 5

    def add(self, m):
        print('self is {0} @D.add'.format(self))
        super().add(m)
        self.n += 5

Qu'affiche le code suivant ?

d = D()
d.add(2)
print(d.n)

Cette fois, la sortie est la suivante :

self is <__main__.D object at 0x10ce10e48> @D.add
self is <__main__.D object at 0x10ce10e48> @B.add
self is <__main__.D object at 0x10ce10e48> @C.add
self is <__main__.D object at 0x10ce10e48> @A.add
19

Avez-vous trouvé la bonne réponse ? Vous pourriez penser que la sortie du code ci-dessus serait similaire à :

self is <__main__.D object at 0x10ce10e48> @D.add
self is <__main__.D object at 0x10ce10e48> @B.add
self is <__main__.D object at 0x10ce10e48> @A.add
15

Pourquoi est-ce différent de ce qui était attendu ? Découvrons ensemble le mystère desuper.

super est une classe

Lorsque nous appelonssuper(), nous instancions en réalité unesuperclasse. Vous avez bien lu,superest une classe, ni un mot-clé ni une fonction ou une autre structure de données :

>>> class A: pass
...
>>> s = super(A)
>>> type(s)
<class 'super'>
>>>

Dans la plupart des cas,supercontient deux éléments d'information très importants : un MRO et une classe dans ce MRO. Lorsqu'on l'appelle de la manière suivantesuper:

super(a_type, obj)

MROfait référence au MRO detype(obj), et la classe dans ce MRO esta_type, et en même tempsisinstance(obj, a_type) == True 。

Lorsqu'il est appelé comme ceci :

super(type1, type2)

Le MRO fait référence àtype2deMRO, MROla classe dans laquelle il se trouvetype1, et en même tempsissubclass(type2, type1) == True 。

Donc,super()que fait-il réellement ? En bref : fournit unMROet unMROclasse dansC , super()retournera un objet qui recherche les méthodes à partir deMROparmiCles classes après lui.

C'est-à-dire que la recherche ne se fait pas comme une méthode régulière qui parcourt toutes lesMROclasses, mais plutôt recherche la queue deMRO.

Par exemple, il y a unMRO:

[A, B, C, D, E, object]

l'appel suivant :

super(C, A).foo()

superne cherchera qu'à partir deC, c'est-à-dire : seulementDouEouobjectsera recherché pour la méthodefoo.

Comment fonctionne super dans l'héritage multiple

Revenons à ce qui précède

d = D()
d.add(2)
print(d.n)

Maintenant, vous avez peut-être une idée de pourquoi la sortie est

self is <__main__.D object at 0x10ce10e48> @D.add
self is <__main__.D object at 0x10ce10e48> @B.add
self is <__main__.D object at 0x10ce10e48> @C.add
self is <__main__.D object at 0x10ce10e48> @A.add
19

, n'est-ce pas ? ;)

Analysons cela en détail ci-dessous :

  • DdeMROest :[D, B, C, A, object] 。 Remarque :Vous pouvez voirD.mro()(Python 2 utiliseD.__mro__) pour voirDdeMROinformations)

  • L'analyse détaillée du code est la suivante :

    class A:
        def __init__(self):
            self.n = 2
    
        def add(self, m):
            # 第四步
            # 来自 D.add 中的 super
            # self == d, self.n == d.n == 5
            print('self is {0} @A.add'.format(self))
            self.n += m
            # d.n == 7
    
    
    class B(A):
        def __init__(self):
            self.n = 3
    
        def add(self, m):
            # 第二步
            # 来自 D.add 中的 super
            # self == d, self.n == d.n == 5
            print('self is {0} @B.add'.format(self))
            # 等价于 suepr(B, self).add(m)
            # self 的 MRO 是 [D, B, C, A, object]
            # 从 B 之后的 [C, A, object] 中查找 add 方法
            super().add(m)
    
            # 第六步
            # d.n = 11
            self.n += 3
            # d.n = 14
    
    class C(A):
        def __init__(self):
            self.n = 4
    
        def add(self, m):
            # 第三步
            # 来自 B.add 中的 super
            # self == d, self.n == d.n == 5
            print('self is {0} @C.add'.format(self))
            # 等价于 suepr(C, self).add(m)
            # self 的 MRO 是 [D, B, C, A, object]
            # 从 C 之后的 [A, object] 中查找 add 方法
            super().add(m)
    
            # 第五步
            # d.n = 7
            self.n += 4
            # d.n = 11
    
    
    class D(B, C):
        def __init__(self):
            self.n = 5
    
        def add(self, m):
            # 第一步
            print('self is {0} @D.add'.format(self))
            # 等价于 super(D, self).add(m)
            # self 的 MRO 是 [D, B, C, A, object]
            # 从 D 之后的 [B, C, A, object] 中查找 add 方法
            super().add(m)
    
            # 第七步
            # d.n = 14
            self.n += 5
            # self.n = 19
    
    d = D()
    d.add(2)
    print(d.n)

    Le diagramme de la procédure d'appel est le suivant :

    D.mro() == [D, B, C, A, object]
    d = D()
    d.n == 5
    d.add(2)
    
    class D(B, C):          class B(A):            class C(A):             class A:
        def add(self, m):       def add(self, m):      def add(self, m):       def add(self, m):
            super().add(m)  1.--->  super().add(m) 2.--->  super().add(m)  3.--->  self.n += m
            self.n += 5   <------6. self.n += 3    <----5. self.n += 4     <----4. <--|
            (14+5=19)               (11+3=14)              (7+4=11)                (5+2=7)

Vous savez maintenant pourquoi d.n vaut 19 après d.add(2), non ? ;)

Source de l'article : https://mozillazg.com/2016/12/python-super-is-not-as-simple-as-you-thought.html