Que fait «super» en Python?


564

Quelle est la différence entre:

class Child(SomeBaseClass):
    def __init__(self):
        super(Child, self).__init__()

et:

class Child(SomeBaseClass):
    def __init__(self):
        SomeBaseClass.__init__(self)

J'ai vu superbeaucoup utilisé dans les classes avec un seul héritage. Je peux voir pourquoi vous l'utiliseriez en héritage multiple mais je ne sais pas quels sont les avantages de l'utiliser dans ce genre de situation.

Réponses:


309

Les avantages de l' super()héritage unique sont minimes - la plupart du temps, vous n'avez pas à coder en dur le nom de la classe de base dans chaque méthode qui utilise ses méthodes parentes.

Cependant, il est presque impossible d'utiliser l'héritage multiple sans super(). Cela inclut les idiomes courants comme les mixins, les interfaces, les classes abstraites, etc. Cela s'étend au code qui étend plus tard le vôtre. Si quelqu'un voulait plus tard écrire une classe étendue Childet un mixin, leur code ne fonctionnerait pas correctement.


6
pouvez-vous donner un exemple par ce que vous voulez dire avec "cela ne fonctionnerait pas correctement"?
Charlie Parker

319

Quelle est la différence?

SomeBaseClass.__init__(self) 

signifie appeler SomeBaseClass's __init__. tandis que

super(Child, self).__init__()

signifie appeler une borne __init__de la classe parent qui suit Childdans l'ordre de résolution de méthode (MRO) de l'instance.

Si l'instance est une sous-classe de Child, il peut y avoir un parent différent qui vient ensuite dans le MRO.

Expliqué simplement

Lorsque vous écrivez une classe, vous souhaitez que d'autres classes puissent l'utiliser. super()permet aux autres classes d'utiliser plus facilement la classe que vous écrivez.

Comme le dit Bob Martin, une bonne architecture vous permet de reporter la prise de décision le plus longtemps possible.

super() peut permettre ce type d'architecture.

Lorsqu'une autre classe sous-classe la classe que vous avez écrite, elle peut également hériter d'autres classes. Et ces classes pourraient avoir un __init__qui vient après cela en __init__fonction de l'ordre des classes pour la résolution de la méthode.

Sans cela, supervous coderiez probablement en dur le parent de la classe que vous écrivez (comme le fait l'exemple). Cela signifierait que vous n'appeleriez pas le suivant __init__dans le MRO et que vous ne pourriez donc pas réutiliser le code qu'il contient.

Si vous écrivez votre propre code pour un usage personnel, cette distinction peut ne pas vous intéresser. Mais si vous voulez que d'autres utilisent votre code, l'utilisation superest une chose qui permet une plus grande flexibilité pour les utilisateurs du code.

Python 2 contre 3

Cela fonctionne en Python 2 et 3:

super(Child, self).__init__()

Cela ne fonctionne que dans Python 3:

super().__init__()

Il fonctionne sans argument en remontant dans le cadre de la pile et en obtenant le premier argument de la méthode (généralement selfpour une méthode d'instance ou clspour une méthode de classe - mais pourrait être d'autres noms) et en trouvant la classe (par exemple Child) dans les variables libres ( il est recherché avec le nom __class__comme variable de fermeture libre dans la méthode).

Je préfère démontrer la manière croisée d'utiliser super, mais si vous utilisez uniquement Python 3, vous pouvez l'appeler sans arguments.

Indirection avec compatibilité ascendante

Qu'est-ce que ça vous donne? Pour l'héritage unique, les exemples de la question sont pratiquement identiques du point de vue de l'analyse statique. Cependant, l'utilisation supervous donne une couche d'indirection avec compatibilité ascendante.

La compatibilité ascendante est très importante pour les développeurs chevronnés. Vous voulez que votre code continue de fonctionner avec des modifications minimales au fur et à mesure que vous le modifiez. Lorsque vous regardez votre historique de révision, vous voulez voir précisément ce qui a changé quand.

Vous pouvez commencer avec un héritage unique, mais si vous décidez d'ajouter une autre classe de base, vous n'avez qu'à changer la ligne avec les bases - si les bases changent dans une classe dont vous héritez (disons qu'un mixin est ajouté) vous changeriez rien dans cette classe. En particulier dans Python 2, obtenir les arguments superet les arguments de méthode corrects peut être difficile. Si vous savez que vous utilisez supercorrectement avec l'héritage unique, cela rend le débogage moins difficile à l'avenir.

Injection de dépendance

D'autres personnes peuvent utiliser votre code et injecter des parents dans la résolution de la méthode:

class SomeBaseClass(object):
    def __init__(self):
        print('SomeBaseClass.__init__(self) called')

class UnsuperChild(SomeBaseClass):
    def __init__(self):
        print('UnsuperChild.__init__(self) called')
        SomeBaseClass.__init__(self)

class SuperChild(SomeBaseClass):
    def __init__(self):
        print('SuperChild.__init__(self) called')
        super(SuperChild, self).__init__()

Supposons que vous ajoutiez une autre classe à votre objet et que vous souhaitiez injecter une classe entre Foo et Bar (à des fins de test ou pour toute autre raison):

class InjectMe(SomeBaseClass):
    def __init__(self):
        print('InjectMe.__init__(self) called')
        super(InjectMe, self).__init__()

class UnsuperInjector(UnsuperChild, InjectMe): pass

class SuperInjector(SuperChild, InjectMe): pass

L'utilisation de l'enfant non super ne parvient pas à injecter la dépendance car l'enfant que vous utilisez a codé en dur la méthode à appeler après la sienne:

>>> o = UnsuperInjector()
UnsuperChild.__init__(self) called
SomeBaseClass.__init__(self) called

Cependant, la classe avec l'enfant qui utilise superpeut injecter correctement la dépendance:

>>> o2 = SuperInjector()
SuperChild.__init__(self) called
InjectMe.__init__(self) called
SomeBaseClass.__init__(self) called

Adresser un commentaire

Pourquoi dans le monde cela serait-il utile?

Python linéarise un arbre d'héritage compliqué via l' algorithme de linéarisation C3 pour créer un ordre de résolution de méthode (MRO).

Nous voulons que les méthodes soient recherchées dans cet ordre .

Pour qu'une méthode définie dans un parent trouve la suivante dans cet ordre sans super, il faudrait

  1. obtenir le mro du type de l'instance
  2. recherchez le type qui définit la méthode
  3. trouver le type suivant avec la méthode
  4. lier cette méthode et l'appeler avec les arguments attendus

Le UnsuperChildne devrait pas avoir accès à InjectMe. Pourquoi la conclusion "évite-t-elle toujours d'utiliser super"? Qu'est-ce que j'oublie ici?

Le UnsuperChildn'a pas accès à InjectMe. C'est la méthode UnsuperInjectorqui a accès à InjectMela méthode dont elle hérite et qui ne peut pas appeler la méthode de cette classe UnsuperChild.

Les deux classes enfants ont l'intention d'appeler une méthode du même nom qui vient ensuite dans le MRO, qui pourrait être une autre classe dont elle n'était pas au courant lorsqu'elle a été créée.

Celui qui ne supercode pas en dur la méthode de son parent - a donc restreint le comportement de sa méthode, et les sous-classes ne peuvent pas injecter de fonctionnalité dans la chaîne d'appel.

Celui qui super a une plus grande flexibilité. La chaîne d'appel pour les méthodes peut être interceptée et la fonctionnalité injectée.

Vous n'avez peut-être pas besoin de cette fonctionnalité, mais les sous-classes de votre code peuvent l'être.

Conclusion

Utilisez toujours superpour référencer la classe parente au lieu de la coder en dur.

Ce que vous avez l'intention de faire, c'est de référencer la classe parente qui est la suivante, pas spécifiquement celle dont vous voyez l'enfant hérité.

Ne pas utiliser superpeut imposer des contraintes inutiles aux utilisateurs de votre code.


En C, DI est comme ça . le code est ici . Si j'ajoute une autre implémentation d' listinterface, disons que doublylinkedlistl'application la sélectionne en douceur. Je peux rendre mon exemple plus configurable en introduisant config.txtet en reliant l'implémentation au moment du chargement. Est-ce le bon exemple? Si oui, comment associer votre code? Voir le premier adv de DI sur wiki. Où une nouvelle implémentation est-elle configurable? dans votre code
surexchange

Une nouvelle implémentation est créée par héritage, par exemple, où l'une des classes "Injector" hérite de la InjectMeclasse. Les commentaires ne sont pas pour la discussion, cependant, je vous suggère de discuter de cela plus en détail avec d'autres dans le chat ou de poser une nouvelle question sur le site principal.
Aaron Hall

très bonne réponse! mais lors de l'utilisation de l'héritage multiple, il y a des complications avec super () et les __init__fonctions. surtout si la signature de __init__varie selon les classes dans la hiérarchie. J'ai ajouté une réponse qui se concentre sur cet aspect
Aviad Rozenhek

35

J'avais joué un peu avec super()et j'avais reconnu que nous pouvions changer l'ordre d'appel.

Par exemple, nous avons la structure hiérarchique suivante:

    A
   / \
  B   C
   \ /
    D

Dans ce cas, MRO de D sera (uniquement pour Python 3):

In [26]: D.__mro__
Out[26]: (__main__.D, __main__.B, __main__.C, __main__.A, object)

Créons une classe où les super()appels après l'exécution de la méthode.

In [23]: class A(object): #  or with Python 3 can define class A:
...:     def __init__(self):
...:         print("I'm from A")
...:  
...: class B(A):
...:      def __init__(self):
...:          print("I'm from B")
...:          super().__init__()
...:   
...: class C(A):
...:      def __init__(self):
...:          print("I'm from C")
...:          super().__init__()
...:  
...: class D(B, C):
...:      def __init__(self):
...:          print("I'm from D")
...:          super().__init__()
...: d = D()
...:
I'm from D
I'm from B
I'm from C
I'm from A

    A
   / 
  B  C
    /
    D

Nous pouvons donc voir que l'ordre de résolution est le même que dans MRO. Mais quand on appelle super()au début de la méthode:

In [21]: class A(object):  # or class A:
...:     def __init__(self):
...:         print("I'm from A")
...:  
...: class B(A):
...:      def __init__(self):
...:          super().__init__()  # or super(B, self).__init_()
...:          print("I'm from B")
...:   
...: class C(A):
...:      def __init__(self):
...:          super().__init__()
...:          print("I'm from C")
...:  
...: class D(B, C):
...:      def __init__(self):
...:          super().__init__()
...:          print("I'm from D")
...: d = D()
...: 
I'm from A
I'm from C
I'm from B
I'm from D

Nous avons un ordre différent, il est inversé un ordre du tuple MRO.

    A
   / 
  B  C
    /
    D 

Pour une lecture supplémentaire, je recommanderais les réponses suivantes:

  1. Exemple de linéarisation C3 avec super (une grande hiérarchie)
  2. Changements de comportement importants entre les anciennes et les nouvelles classes de style
  3. L'histoire intérieure sur les classes de nouveau style

Je ne comprends pas pourquoi la commande change. La première partie, je comprends que DBCA parce que D est la première classe, puis lorsque charger l'auto (B, C) imprimera finalement B, C puis seulement A puisque B (A), C (A) pointé vers soi pour la finale partie. Si je respecte cette compréhension, la deuxième partie ne devrait-elle pas être comme BCAD? Pourriez-vous s'il vous plaît m'expliquer un peu s'il vous plaît.
JJson

Mon mauvais, je n'ai pas remarqué que chaque instance de chaque classe a été lancée en premier avec super (). Si tel est le cas, ne devrait-il pas s'agir d'ABCD? Je comprends en quelque sorte comment l'ACBD est arrivé mais je ne pouvais toujours pas convaincre et j'ai encore un peu de confusion. ma compréhension est que, d = D () a appelé la classe D (B, C) avec 2 auto-paramètres, puisque super () est d'abord lancé puis B est appelé avec ses attributs puis D n'est pas imprimé avant C parce que la classe est D (B, C) contient 2 auto-paramètres, il doit donc exécuter le second qui est de classe C (A), après exécution, il n'y a plus de self-paramètres à exécuter
JJson

Cela se base sur la définition mro .
SKhalymon

1
puis il imprimera C puis imprimera B et enfin imprimera D. Ai-je raison?
JJson

2
Il est très facile de comprendre le second tant que vous obtenez le premier. C'est comme une pile. vous poussez l'impression '' dans la pile et faites super (), quand c'est fait le A, il commence à imprimer les choses dans cette pile, donc l'ordre est inverse.
accordez le soleil

35

Tout cela ne suppose-t-il pas que la classe de base est une classe de nouveau style?

class A:
    def __init__(self):
        print("A.__init__()")

class B(A):
    def __init__(self):
        print("B.__init__()")
        super(B, self).__init__()

Ne fonctionnera pas en Python 2. class Adoit être de nouveau style, c'est-à-dire:class A(object)


20

Lorsque vous appelez super()pour résoudre la version d'un parent d'une méthode de classe, d'une méthode d'instance ou d'une méthode statique, nous voulons passer la classe actuelle dont la portée est dans le premier argument, pour indiquer la portée du parent que nous essayons de résoudre et en tant que un deuxième argument l'objet d'intérêt pour indiquer à quel objet nous essayons d'appliquer cette portée.

Considérons une hiérarchie de classes A, Bet Coù chaque classe est le parent de celui qui le suit, et a, bet cinstances de chaque respectives.

super(B, b) 
# resolves to the scope of B's parent i.e. A 
# and applies that scope to b, as if b was an instance of A

super(C, c) 
# resolves to the scope of C's parent i.e. B
# and applies that scope to c

super(B, c) 
# resolves to the scope of B's parent i.e. A 
# and applies that scope to c

Utilisation superavec une méthode statique

par exemple en utilisant super()de l'intérieur de la __new__()méthode

class A(object):
    def __new__(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        return super(A, cls).__new__(cls, *a, **kw)

Explication:

1- même s'il est habituel __new__()de prendre comme premier paramètre une référence à la classe appelante, il n'est pas implémenté en Python comme méthode de classe, mais plutôt comme méthode statique. Autrement dit, une référence à une classe doit être passée explicitement comme premier argument lors de l'appel __new__()direct:

# if you defined this
class A(object):
    def __new__(cls):
        pass

# calling this would raise a TypeError due to the missing argument
A.__new__()

# whereas this would be fine
A.__new__(A)

2- lors de l'appel super()pour accéder à la classe parent, nous passons la classe enfant Acomme premier argument, puis nous passons une référence à l'objet d'intérêt, dans ce cas, c'est la référence de classe qui a été transmise lors de l' A.__new__(cls)appel. Dans la plupart des cas, il s'agit également d'une référence à la classe enfant. Dans certaines situations, cela peut ne pas être le cas, par exemple dans le cas d'héritages de plusieurs générations.

super(A, cls)

3- puisque comme une règle générale __new__()est une méthode statique, super(A, cls).__new__retourne également une méthode statique et doit être fourni explicitement tous les arguments, y compris la référence à l'objet de insterest, dans ce cas cls.

super(A, cls).__new__(cls, *a, **kw)

4- Faire la même chose sans super

class A(object):
    def __new__(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        return object.__new__(cls, *a, **kw)

Utilisation superavec une méthode d'instance

par exemple en utilisant super()de l'intérieur__init__()

class A(object): 
    def __init__(self, *a, **kw):
        # ...
        # you make some changes here
        # ...

        super(A, self).__init__(*a, **kw)

Explication:

1- __init__est une méthode d'instance, ce qui signifie qu'elle prend comme premier argument une référence à une instance. Lorsqu'elle est appelée directement à partir de l'instance, la référence est transmise implicitement, c'est-à-dire que vous n'avez pas besoin de la spécifier:

# you try calling `__init__()` from the class without specifying an instance
# and a TypeError is raised due to the expected but missing reference
A.__init__() # TypeError ...

# you create an instance
a = A()

# you call `__init__()` from that instance and it works
a.__init__()

# you can also call `__init__()` with the class and explicitly pass the instance 
A.__init__(a)

2- lors de l'appel à l' super()intérieur, __init__()nous passons la classe enfant comme premier argument et l'objet d'intérêt comme deuxième argument, qui en général est une référence à une instance de la classe enfant.

super(A, self)

3- L'appel super(A, self)renvoie un proxy qui résoudra la portée et l'appliquera selfcomme s'il s'agissait désormais d'une instance de la classe parente. Appelons ce proxy s. Puisque __init__()c'est une méthode d'instance, l'appel s.__init__(...)passera implicitement une référence de selfcomme premier argument à celle du parent __init__().

4- pour faire de même sans superavoir besoin de passer explicitement une référence à une instance à la version parent de __init__().

class A(object): 
    def __init__(self, *a, **kw):
        # ...
        # you make some changes here
        # ...

        object.__init__(self, *a, **kw)

Utilisation superavec une méthode de classe

class A(object):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        print "A.alternate_constructor called"
        return cls(*a, **kw)

class B(A):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        print "B.alternate_constructor called"
        return super(B, cls).alternate_constructor(*a, **kw)

Explication:

1- Une méthode de classe peut être appelée directement depuis la classe et prend comme premier paramètre une référence à la classe.

# calling directly from the class is fine,
# a reference to the class is passed implicitly
a = A.alternate_constructor()
b = B.alternate_constructor()

2- Lorsque nous appelons super()dans une méthode de classe pour résoudre la version de son parent, nous voulons passer la classe enfant actuelle comme premier argument pour indiquer la portée du parent que nous essayons de résoudre et l'objet d'intérêt comme deuxième argument pour indiquer à quel objet nous voulons appliquer cette portée, qui est en général une référence à la classe enfant elle-même ou à l'une de ses sous-classes.

super(B, cls_or_subcls)

3- L'appel super(B, cls)résout le champ d'application Aet l'applique cls. Puisque alternate_constructor()c'est une méthode de classe, l'appel super(B, cls).alternate_constructor(...)passera implicitement une référence de clscomme premier argument à Ala version dealternate_constructor()

super(B, cls).alternate_constructor()

4- Pour faire de même sans utiliser, super()vous devez obtenir une référence à la version non liée de A.alternate_constructor()(c'est-à-dire la version explicite de la fonction). Faire simplement cela ne fonctionnerait pas:

class B(A):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        print "B.alternate_constructor called"
        return A.alternate_constructor(cls, *a, **kw)

Ce qui précède ne fonctionnerait pas car la A.alternate_constructor()méthode prend une référence implicite à Acomme premier argument. L' clsêtre passé ici serait son deuxième argument.

class B(A):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        print "B.alternate_constructor called"
        # first we get a reference to the unbound 
        # `A.alternate_constructor` function 
        unbound_func = A.alternate_constructor.im_func
        # now we call it and pass our own `cls` as its first argument
        return unbound_func(cls, *a, **kw)

6

Beaucoup de bonnes réponses, mais pour les apprenants visuels: permet d'abord d'explorer avec des arguments à super, puis sans. exemple d'arbre de super héritage

Imaginez qu'il existe une instance jackcréée à partir de la classe Jack, qui possède la chaîne d'héritage comme indiqué en vert dans l'image. Appel:

super(Jack, jack).method(...)

utilisera le MRO (Method Resolution Order) de jack(son arbre d'héritage dans un certain ordre), et commencera la recherche à partir de Jack. Pourquoi peut-on fournir une classe parent? Eh bien, si nous commençons à chercher à partir de l'instance jack, il trouverait la méthode d'instance, le but est de trouver sa méthode parente.

Si l'on ne fournit pas d'arguments à super, c'est comme le premier argument passé est la classe de self, et le deuxième argument passé est self. Ceux-ci sont calculés automatiquement pour vous en Python3.

Cependant, disons que nous ne voulons pas utiliser Jackla méthode de, au lieu de passer Jack, nous pourrions passer Jenpour commencer à chercher la méthode à partir de Jen.

Il recherche une couche à la fois (largeur pas de profondeur), par exemple si Adamet Suetous les deux ont la méthode désirée, celle de Suese trouver en premier.

Si Cainet les Suedeux avaient la méthode requise, Cainla méthode de serait appelée en premier. Cela correspond en code à:

Class Jen(Cain, Sue):

MRO est de gauche à droite.


2

quelques bonnes réponses ici, mais elles n'abordent pas comment utiliser super()dans le cas où différentes classes de la hiérarchie ont des signatures différentes ... en particulier dans le cas de__init__

pour répondre à cette partie et être en mesure de l'utiliser efficacement, super()je suggère de lire ma réponse super () et de changer la signature des méthodes coopératives .

voici juste la solution à ce scénario:

  1. les classes de niveau supérieur de votre hiérarchie doivent hériter d'une classe personnalisée comme SuperObject:
  2. si les classes peuvent prendre des arguments différents, passez toujours tous les arguments que vous avez reçus à la super fonction en tant qu'arguments de mot-clé et acceptez toujours **kwargs.
class SuperObject:        
    def __init__(self, **kwargs):
        print('SuperObject')
        mro = type(self).__mro__
        assert mro[-1] is object
        if mro[-2] is not SuperObject:
            raise TypeError(
                'all top-level classes in this hierarchy must inherit from SuperObject',
                'the last class in the MRO should be SuperObject',
                f'mro={[cls.__name__ for cls in mro]}'
            )

        # super().__init__ is guaranteed to be object.__init__        
        init = super().__init__
        init()

exemple d'utilisation:

class A(SuperObject):
    def __init__(self, **kwargs):
        print("A")
        super(A, self).__init__(**kwargs)

class B(SuperObject):
    def __init__(self, **kwargs):
        print("B")
        super(B, self).__init__(**kwargs)

class C(A):
    def __init__(self, age, **kwargs):
        print("C",f"age={age}")
        super(C, self).__init__(age=age, **kwargs)

class D(B):
    def __init__(self, name, **kwargs):
        print("D", f"name={name}")
        super(D, self).__init__(name=name, **kwargs)

class E(C,D):
    def __init__(self, name, age, *args, **kwargs):
        print( "E", f"name={name}", f"age={age}")
        super(E, self).__init__(name=name, age=age, *args, **kwargs)

E(name='python', age=28)

production:

E name=python age=28
C age=28
A
D name=python
B
SuperObject

0
class Child(SomeBaseClass):
    def __init__(self):
        SomeBaseClass.__init__(self)

C'est assez facile à comprendre.

class Child(SomeBaseClass):
    def __init__(self):
        super(Child, self).__init__()

Ok, que se passe-t-il maintenant si vous utilisez super(Child,self)?

Lorsqu'une instance enfant est créée, son MRO (Method Resolution Order) est dans l'ordre de (Child, SomeBaseClass, object) en fonction de l'héritage. (supposez que SomeBaseClass n'a pas d'autres parents à l'exception de l'objet par défaut)

En passant Child, self, superrecherche dans le MRO de l' selfinstance et retourne l'objet proxy à côté de Child, dans ce cas c'est SomeBaseClass, cet objet invoque alors la __init__méthode de SomeBaseClass. En d'autres termes, si c'est le cas super(SomeBaseClass,self), l'objet proxy qui superretourne seraitobject

Pour l'héritage multiple, le MRO peut contenir de nombreuses classes, supervous permet donc de décider où vous souhaitez commencer la recherche dans le MRO.


0

Considérez le code suivant:

class X():
    def __init__(self):
        print("X")

class Y(X):
    def __init__(self):
        # X.__init__(self)
        super(Y, self).__init__()
        print("Y")

class P(X):
    def __init__(self):
        super(P, self).__init__()
        print("P")

class Q(Y, P):
    def __init__(self):
        super(Q, self).__init__()
        print("Q")

Q()

Si vous changez de constructeur Yen X.__init__, vous obtiendrez:

X
Y
Q

Mais en utilisant super(Y, self).__init__(), vous obtiendrez:

X
P
Y
Q

Et Pou Qpeut même être impliqué à partir d'un autre fichier que vous ne connaissez pas lorsque vous écrivez Xet Y. Donc, fondamentalement, vous ne saurez pas à quoi super(Child, self)fera référence lorsque vous écrivez class Y(X), même la signature de Y est aussi simple que Y(X). C'est pourquoi super pourrait être un meilleur choix.

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.