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


414
2017-10-21 18:13


origine


Réponses:


Les avantages de super() dans l'héritage simple 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 tels que les mixins, les interfaces, les classes abstraites, etc. Cela s'étend au code qui étend les vôtres par la suite. Si quelqu'un voulait écrire un cours plus tard, Child et un mixin, leur code ne fonctionnerait pas correctement.


244
2017-10-21 18:24



Quelle est la différence?

SomeBaseClass.__init__(self) 

moyens d'appeler SomeBaseClassde __init__. tandis que

super(Child, self).__init__()

signifie appeler une borne __init__ de la classe parent qui suit Child dans l'instance 'Order Resolution Order (MRO)'.

Si l'instance est une sous-classe de Child, il se peut qu'un autre parent apparaisse dans le 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 aussi longtemps que possible.

super() peut activer 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__ cela vient après __init__ basé sur l'ordre des classes pour la résolution de méthode.

Sans pour autant super vous devriez probablement coder en dur le parent de la classe que vous écrivez (comme le fait l'exemple). Cela voudrait dire que vous n'appelez pas le prochain __init__ dans le MRO, et vous ne pourriez donc pas réutiliser le code dedans.

Si vous écrivez votre propre code pour un usage personnel, vous ne vous souciez peut-être pas de cette distinction. Mais si vous voulez que d’autres utilisent votre code, utilisez super est une chose qui permet une plus grande flexibilité pour les utilisateurs du code.

Python 2 contre 3

Cela fonctionne dans Python 2 et 3:

super(Child, self).__init__()

Cela ne fonctionne que dans Python 3:

super().__init__()

Il fonctionne sans arguments en remontant 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 pourrait être d'autres noms) et trouver la classe (par ex. Child) dans les variables libres (il est recherché avec le nom __class__ comme une variable de fermeture libre dans la méthode).

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

Indirection avec compatibilité avant

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

La compatibilité directe est très importante pour les développeurs expérimentés. Vous voulez que votre code continue de fonctionner avec un minimum de modifications lorsque 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 l'héritage unique, mais si vous décidez d'ajouter une autre classe de base, vous devez seulement changer la ligne avec les bases - si les bases changent dans une classe dont vous héritez (dites un mixin est ajouté) vous changeriez rien dans cette classe. Particulièrement dans Python 2, obtenir les arguments pour super et les bons arguments de méthode peuvent être difficiles. Si vous savez que vous utilisez super correctement avec l'héritage unique, ce qui rend le débogage moins difficile à aller de l'avant.

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 ajoutez 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 n'injecte pas la dépendance car l'enfant que vous utilisez a codé en dur la méthode qui doit être appelée 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

Adresser un commentaire

Pourquoi dans le monde cela serait-il utile?

Python linéarise un arbre d'héritage compliqué via le 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 une méthode définie dans un parent pour trouver la suivante dans cet ordre sans super, il faudrait

  1. obtenir le mro du type de l'instance
  2. chercher 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 UnsuperChild ne devrait pas avoir accès à InjectMe. Pourquoi n’est-ce pas la conclusion "Évitez toujours d’utiliser super"? Qu'est-ce que j'oublie ici?

le UnsuperChild Est-ce que ne pas avoir accès à InjectMe. C'est le UnsuperInjector qui a accès à InjectMe - et pourtant ne peut pas appeler la méthode de cette classe à partir de la méthode dont elle hérite UnsuperChild.

Les deux classes Child ont l’intention d’appeler une méthode du même nom que la suivante, qui pourrait être un autre classe, il n'était pas au courant de quand il a été créé.

Celui sans super hard-codes méthode de son parent - est donc a restreint le comportement de sa méthode, et les sous-classes ne peuvent pas injecter des fonctionnalités dans la chaîne d'appel.

Celui avec  super a plus de 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

Toujours utiliser super pour référencer la classe parent au lieu de le coder en dur.

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

N'utilise pas super peut mettre des contraintes inutiles sur les utilisateurs de votre code.


212
2017-11-02 00:53



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 nouveau, c'est-à-dire: class A(object)


34
2017-10-22 00:06



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

Par exemple, nous avons la structure de hiérarchie suivante:

    A
   / \
  B   C
   \ /
    D

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

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

Créons une classe où super() appels après 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 style nouveau

19
2017-12-29 17:31



En appelant super() pour résoudre en une version parente d'une méthode de classe, d'une méthode d'instance ou d'une méthode statique, nous voulons passer la classe en cours dont nous sommes le premier argument, pour indiquer la portée du parent auquel nous essayons de résoudre, et en second lieu 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 celui qui la suit, et a, b, et c instances respectives de chacun.

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

En utilisant super avec une méthode statique

par exemple. en utilisant super() de l'intérieur __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 si c'est habituel __new__() prendre comme premier paramètre une référence à la classe appelante, il est ne pas implémenté en Python comme une méthode de classe, mais plutôt comme une méthode statique. Autrement dit, une référence à une classe doit être passée explicitement comme premier argument lors de l'appel __new__() directement:

# 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 arriver à la classe parente on passe la classe enfant A comme son premier argument, alors 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é adoptée lorsque A.__new__(cls) a été appelé. Dans la plupart des cas, il se trouve également être une référence à la classe enfant. Dans certaines situations, il peut ne pas l'être, par exemple dans le cas d'héritages à générations multiples.

super(A, cls)

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

En utilisant super avec 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 depuis 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- en appelant super() dans __init__() nous passons la classe enfant comme premier argument et l'objet d'intérêt comme deuxième 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 va résoudre la portée et l'appliquer à self comme si c'était maintenant une instance de la classe parent. Appelons ce proxy s. Depuis __init__() est une méthode d'instance l'appel s.__init__(...) passera implicitement une référence de self comme le premier argument à la mère __init__().

4- faire la même chose sans super nous devons transmettre une référence à une instance explicitement à la version du parent de __init__().

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

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

En utilisant 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 à partir de 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- en appelant 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. Depuis alternate_constructor() est un classmethod l'appel super(B, cls).alternate_constructor(...) passera implicitement une référence de cls comme le premier argument à Ala version de alternate_constructor()

super(B, cls).alternate_constructor()

4- faire de même sans utiliser super() vous auriez besoin d'une référence à la non lié version 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 parce que le A.alternate_constructor() méthode prend une référence implicite à A comme premier argument. le 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)

15
2017-09-07 17:33



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 Child est créée, son MRO (Method Resolution Order) est dans l'ordre de (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 recherches dans le MRO du self instance, et retourne l'objet proxy à côté de Child, dans ce cas c'est SomeBaseClass, cet objet invoque alors __init__ méthode de SomeBaseClass. En d'autres termes, si c'est super(SomeBaseClass,self), l'objet proxy super les retours seraient object 

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


0
2017-07-17 19:14