Question

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 comprends pas vraiment les avantages de l'utiliser dans ce genre de situation.

Était-ce utile?

La solution

Les avantages de super () dans un 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 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 ultérieurement, quelqu'un souhaitait écrire une classe comportant Child et un mixin, son code ne fonctionnerait pas correctement.

Autres conseils

Quelle est la différence?

SomeBaseClass.__init__(self) 

signifie appeler le __ init __ de SomeBaseClass . alors que

super(Child, self).__init__()

signifie appeler un __ init __ lié ??à partir de la classe parent qui suit Enfant dans l'ordre de résolution de la 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 reporter la prise de décision le plus longtemps possible.

super () peut activer ce type d'architecture.

Lorsqu'une autre classe sous-classe la classe que vous avez écrite, il se peut également qu'elle hérite d'autres classes. Et ces classes pourraient avoir un __ init __ qui suit 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'appelleriez pas le prochain __ init __ dans le MRO, et vous ne pourriez donc pas réutiliser le code qu'il contient.

Si vous écrivez votre propre code pour votre usage personnel, cette distinction ne vous concerne peut-être pas. Mais si vous souhaitez que les autres utilisateurs utilisent votre code, utiliser super est une chose qui offre une plus grande flexibilité aux utilisateurs du code.

Python 2 contre 3

Cela fonctionne dans Python 2 et 3:

super().__init__()

Ceci ne fonctionne que dans Python 3:

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__()

Cela fonctionne sans arguments 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 cela pourrait être d'autres noms) et trouver 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 façon compatible d'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 d'un point de vue d'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 minimes à 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 seul héritage, 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é) ne changerais 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 héritage unique, le débogage est plus facile à réaliser.

Injection de dépendance

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

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

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):

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

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:

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

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

<*>

Adressant 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. récupère le mro du type de l'instance
  2. recherchez le type qui définit la méthode
  3. trouvez le type suivant avec la méthode
  4. liez cette méthode et appelez-la avec les arguments attendus
  

Le UnsuperChild ne doit pas avoir accès à InjectMe . Pourquoi la conclusion n’est-elle pas toujours d'éviter d'utiliser super " Qu'est-ce qui me manque ici?

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

Les deux classes enfant ont l'intention d'appeler une méthode du même nom que celle qui suit dans la MRO, ce qui pourrait être une autre classe dont elle ignorait la 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.

Celui avec super offre 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 des sous-classeurs de votre code peuvent le faire.

Conclusion

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

Ce que vous souhaitez, c'est faire référence à la classe parente next-in-line, 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.

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

J'avais un peu joué avec super () et je savais 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

Nous pouvons donc constater 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 en inversant l'ordre du tuple MRO.

    A
   / ⇘
  B ⇐ C
   ⇘ /
    D 

Pour des lectures supplémentaires, je vous 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 New- Classes de styles

Lorsque vous appelez super () pour le résoudre en version d'un parent d'une méthode de classe, d'une méthode d'instance ou de 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 à quel objet nous essayons d'appliquer cette portée.

Considérons 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

Utilisation de 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 n'est pas implémenté dans Python en tant que méthode de classe, mais plutôt une 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- lors de l'appel de 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 de Dans ce cas, c’est la référence de la classe qui a été transmise lors de l’appel de A .__ new __ (cls) . 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 générations multiples.

super(A, cls)

3- puisque, 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 tous les arguments explicitement, y compris la référence à l'objet insterest, dans ce cas cls .

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

4- Faites 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'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 passée implicitement. 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 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 à self comme s'il s'agissait maintenant 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. au __ init __ () du parent.

4- Pour faire la même chose sans super , nous devons passer explicitement une référence à une instance dans la version parent de __ init __ () .

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

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

Utilisation de 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- lors de l'appel de super () dans une méthode de classe pour résoudre la version de son parent, nous voulons passer la classe enfant actuelle en tant que premier argument pour indiquer la portée du parent que nous essayons à résoudre et l'objet d'intérêt en tant que deuxième argument pour indiquer l'objet auquel 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 substit_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 non liée 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. Le cls qui est 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)
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 (Enfant, auto) ?

Lorsqu'une instance enfant est créée, son ordre de résolution de méthode (MRO) est dans l'ordre suivant (Child, SomeBaseClass, object) en fonction de l'héritage. (supposons que SomeBaseClass n’ait pas d’autres parents à l’exception de l’objet par défaut)

En passant Child, self , super recherche dans le MRO de l'instance self et renvoie l'objet proxy à côté de Child, dans Dans ce cas, il s'agit de SomeBaseClass. Cet objet appelle ensuite la méthode __ init __ de SomeBaseClass. En d’autres termes, s’il s’agit de super (SomeBaseClass, self) , l’objet proxy renvoyé par super serait objet

Pour l'héritage multiple, le MRO peut contenir de nombreuses classes. Par conséquent, super vous permet de choisir le point de départ de la recherche dans le MRO.

Quelques bonnes réponses ici, mais elles ne décrivent 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ère de lire ma réponse super () et 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 telle que SuperObjet :
  2.   
  3. 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 de mot clé et acceptez toujours ** kwargs .
  4.   
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)

sortie:

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

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

Imaginez-vous une instance jack créée à partir de la classe Jack , qui possède la chaîne d'héritage telle qu'elle apparaît en vert dans l'image. Appel:

super (prise, prise). méthode (...)

utilisera le MRO (Method Resolution Order) de jack (son arborescence d'héritage dans un certain ordre) et lancera la recherche à partir de 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 l'on ne fournit pas d'arguments à super, son principe est semblable au premier argument transmis: la classe self , et le deuxième argument transmis est 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 au début rechercher la méthode à partir de Jen .

Il recherche une couche à la fois (largeur et non pas profondeur), par ex. si Adam et Sue ont tous deux 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.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top