web-dev-qa-db-fra.com

Que fait 'super' en Python?

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 souvent vu super être utilisé dans des classes avec un seul héritage. Je peux comprendre pourquoi vous l'utiliseriez dans un héritage multiple, mais je ne vois pas vraiment quels sont les avantages de l'utiliser dans ce genre de situation.

462
user25785

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

Cependant, il est presque impossible d'utiliser l'héritage multiple sans super(). Cela inclut les expressions courantes telles que mixins, interfaces, classes abstraites, etc. Cela s'étend au code qui étend plus tard le vôtre. Si plus tard, quelqu'un souhaitait écrire une classe qui étendait Child et un mixin, leur code ne fonctionnerait pas correctement.

267
John Millikin

Quelle est la différence?

SomeBaseClass.__init__(self) 

signifie appeler SomeBaseClass '__init__. tandis que

super(Child, self).__init__()

signifie appeler un __init__ lié à partir de la classe parente qui suit Child dans l'ordre de résolution de méthode (MRO) de l'instance. 

Si l'instance est une sous-classe de Child, il se peut qu'un parent différent vienne ensuite dans la MRO. 

Expliqué simplement

Lorsque vous écrivez une classe, vous voulez 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 différer le plus possible la prise de décision.

super() peut activer ce type d'architecture.

Lorsqu'une autre classe sous-classe la classe que vous avez écrite, il peut également s'agir d'un héritage d'autres classes. Et ces classes pourraient avoir un __init__ qui vient après ce __init__ en fonction de l'ordre des classes pour la résolution de la méthode.

Sans super, vous 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 prochain __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 votre usage personnel, cette distinction peut ne pas vous intéresser. Mais si vous souhaitez que d'autres personnes utilisent votre code, utiliser super est une chose qui offre une plus grande flexibilité aux 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__()

Cela fonctionne sans argument en montant dans le cadre de la pile et en obtenant le premier argument de la méthode (généralement self pour une méthode d'instance ou cls pour une méthode de classe - mais il pourrait s'agir d'autres noms) et la classe (par exemple Child) dans les variables libres (le nom __class__ est recherché comme variable de fermeture libre dans la méthode).

Je préfère montrer la méthode de compatibilité croisée consistant à utiliser super, mais si vous utilisez uniquement Python 3, vous pouvez l'appeler sans argument.

Indirection avec compatibilité en aval

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

La compatibilité en aval est très importante pour les développeurs chevronnés. Vous voulez que votre code continue à fonctionner avec des modifications minimales au fur et à mesure que vous le modifiez. Lorsque vous examinez l'historique de vos révisions, vous souhaitez savoir 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, il vous suffit de changer la ligne avec les bases. Si les bases changent dans une classe dont vous héritez (par exemple, un mixin est ajouté), vous devez changer. rien dans cette classe. En particulier dans Python 2, il peut être difficile d'obtenir les arguments de super et les arguments de méthode corrects. Si vous savez que vous utilisez correctement super avec un seul héritage, le débogage est moins difficile.

Injection de dépendance

D'autres personnes peuvent utiliser votre code et impliquer les 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 vouliez injecter une classe entre Foo et Bar (à des fins de test ou pour une 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 super peut correctement injecter la dépendance:

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

Répondre à 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, elle doit 

  1. récupère le mro du type de l'instance 
  2. cherche 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

La UnsuperChild ne devrait pas avoir accès à InjectMe. Pourquoi la conclusion "évitez toujours d'utiliser super"? Qu'est-ce que j'oublie ici?

La UnsuperChild n'a pas accès à InjectMe. C'est le UnsuperInjector qui a accès à InjectMe - et qui ne peut pas encore appeler la méthode de cette classe à partir de la méthode dont il hérite de UnsuperChild

Les deux classes Child ont l'intention d'appeler une méthode du même nom que celle qui suit dans la MRO, qui pourrait être une autre classe dont elle n'était pas consciente au moment de sa création. 

Celui sans super code 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és dans la chaîne d'appels.

Le avec _ ​​[VARIABLE] _ a une plus grande flexibilité. La chaîne d’appel pour les méthodes peut être interceptée et des fonctionnalités injectées.

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

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

Ce que vous souhaitez, c'est faire référence à la classe parente suivante, pas spécifiquement à celle dont vous voyez l'enfant hériter.

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

Not using super can put unnecessary constraints on users of your code.

245
Aaron Hall

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

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 A doit être d'un nouveau style, c'est-à-dire: class A(object)

35
mhawke

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

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ù super() appelle 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

On voit donc 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, c’est l’inversion de la MRO Tuple.

    A
   / ⇘
  B ⇐ C
   ⇘ /
    D 

Pour des lectures supplémentaires, 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 de l'intérieur sur les classes de style nouveau
25
Infernion

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 transmettre la classe actuelle dont la portée est le premier argument, pour indiquer la portée du parent que nous essayons de résoudre, et comme deuxième argument, l'objet d'intérêt pour indiquer l'objet auquel nous essayons d'appliquer cette portée.

Prenons une hiérarchie de classes A, B et C où chaque classe est le parent de celle qui la suit et a, b et c de chaque instance.

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

Utiliser super avec une méthode statique

par exemple. Utilisation de super() depuis la méthode __new__()

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 pour __new__() de prendre comme premier paramètre une référence à la classe appelante, il est non implémenté en Python en tant que méthode de classe, mais plutôt en tant que méthode statique. Autrement dit, une référence à une classe doit être explicitement passée en tant que premier argument lors de l'appel direct de __new__():

# 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- en appelant super() pour accéder à la classe parente, nous passons la classe enfant A en tant que premier argument, puis nous passons une référence à l'objet qui nous intéresse, dans ce cas c'est la référence de classe qui a été passée lorsque A.__new__(cls) a été appelé. Dans la plupart des cas, il s’agit également d’une référence à la classe enfant. Dans certains cas, cela peut ne pas être le cas, par exemple dans le cas d'héritages de plusieurs générations.

super(A, cls)

3- étant donné qu'en règle générale, __new__() est une méthode statique, super(A, cls).__new__ renverra également une méthode statique et doit être fourni explicitement avec tous les arguments, y compris la référence à l'objet 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 de super avec une méthode d'instance

par exemple. en utilisant super() depuis __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'il 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 passée 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 de super() dans __init__(), nous passons la classe enfant en tant que premier argument et l'objet d'intérêt en tant que second argument, qui est en général 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 à self comme s'il s'agissait à présent d'une instance de la classe parente. Appelons ce proxy s. Puisque __init__() est une méthode d'instance, l'appel s.__init__(...) passera implicitement une référence de self en tant que premier argument de la fonction __init__() du parent.

4- pour faire la même chose sans super, nous devons 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)

Utiliser super avec 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 second. 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 la portée de A et l'applique à cls. Puisque alternate_constructor() est une méthode de classe, l'appel super(B, cls).alternate_constructor(...) passera implicitement une référence de cls comme premier argument de la version de A de alternate_constructor()

super(B, cls).alternate_constructor()

4- pour faire la même chose sans utiliser super(), vous devez obtenir une référence à la version unbound de A.alternate_constructor() (c'est-à-dire la version explicite de la fonction). 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 méthode A.alternate_constructor() prend une référence implicite à A comme premier argument. La cls passée 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)
15
Michael Ekoka

Beaucoup de bonnes réponses, mais pour les apprenants visuels: explorons d'abord avec des arguments super, puis sans. super inheritance tree example

Imaginez-vous une instance jack créée à partir de la classe Jack, qui possède la chaîne d'héritage affichée 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 lancera la recherche dans Jack. Pourquoi peut-on fournir une classe de parents? Eh bien, si nous commençons la recherche à partir de l’instance jack, la méthode de l’instance sera trouvée; l’essentiel est de trouver sa méthode des parents.

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

Cependant, disons que nous ne voulons pas utiliser la méthode de Jack, au lieu de passer à Jack, nous pourrions passer à Jen pour commencer à rechercher la méthode de Jen vers le haut.

Il recherche une couche à la fois (largeur et profondeur), par ex. Si Adam et Sue ont la méthode requise, celle de Sue sera trouvée en premier.

Si Cain et Sue avaient tous deux la méthode requise, la méthode de Cain serait appelée en premier. Cela correspond dans le code à:

Class Jen(Cain, Sue):

MRO est de gauche à droite.

1
run_the_race
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 ordre de résolution de méthode est dans l'ordre suivant: (Child, SomeBaseClass, object) en fonction de l'héritage. (supposons que SomeBaseClass n'a pas d'autres parents à l'exception de l'objet par défaut)

En passant Child, self, super cherche dans le MRO de l'occurrence self et renvoie l'objet proxy next de Child, dans ce cas il s'agit de SomeBaseClass, cet objet appelle ensuite la méthode __init__ de SomeBaseClass. Dans Word, s'il s'agit de super(SomeBaseClass,self), l'objet proxy renvoyé par super serait object 

En cas d'héritage multiple, le MRO peut contenir de nombreuses classes. super vous permet donc de choisir le point de départ de la recherche dans le MRO.

0
dorisxx

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 pouvoir utiliser efficacement super(), je vous suggérerais de lire ma réponse super () et de modifier 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 telle que SuperObject:
  2. si les classes peuvent prendre des arguments différents, transmettez toujours tous les arguments que vous avez reçus à la super-fonction en tant qu'arguments 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.__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)

sortie:

E name=python age=28
C age=28
A
D name=python
B
SuperObject
0
Aviad Rozenhek