Qu'est-ce que "super" fait 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 vois pourquoi vous l'utiliseriez dans les héritages multiples, mais je ne sais pas quels sont les avantages de l'utiliser dans ce genre de situation.
6 réponses
les avantages de super()
en mono-héritage sont minimes -- la plupart du temps, vous n'avez pas à coder 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 expressions idiomatiques courantes comme les mixins, les interfaces, Les classes abstraites, etc. Ceci s'étend au code qui étend plus tard le vôtre. Si quelqu'un voulait plus tard écrire une classe qui s'étendait Child
et un mixin, leur code ne fonctionnerait pas correctement.
quelle différence?
SomeBaseClass.__init__(self)
signifie appeler SomeBaseClass
's __init__
. tandis que
super(Child, self).__init__()
signifie appeler un __init__
relié de la classe mère qui suit Child
dans L'ordre de la méthode de résolution de l'instance (MRO).
si l'instance est une sous-classe D'enfant, il peut y avoir un parent différent qui vient ensuite dans le MRO.
expliqué simplement
quand vous écrivez une classe, vous voulez que d'autres classes puissent l'utiliser. super()
rend plus facile pour les autres classes d'utiliser la classe que vous écrivez.
comme le dit Bob Martin, une bonne architecture permet de reporter la prise de décision aussi longtemps que possible.
super()
peut permettre ce genre d'architecture.
quand une autre classe sous-classe la classe que vous avez écrite, elle pourrait aussi hériter d'autres classes. Et ces classes pourraient avoir un __init__
qui vient après ce __init__
basé sur l'ordre des classes pour la résolution de méthode.
sans super
vous coderiez probablement 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 que vous ne pourriez donc pas réutiliser le code dans celui-ci.
si vous écrivez votre propre code pour usage personnel, vous ne vous souciez peut-être pas de cette distinction. Mais si vous voulez que d'autres utilisent votre code, Utiliser super
est une chose qui permet une plus grande flexibilité pour les utilisateurs du code.
Python 2 et 3
cela fonctionne en Python 2 et 3:
super(Child, self).__init__()
cela ne fonctionne qu'en Python 3:
super().__init__()
il fonctionne sans arguments en se déplaçant vers le haut dans le cadre de la pile et d'obtenir le premier argument à la méthode (habituellement self
pour une méthode d'instance ou cls
pour une méthode de classe - mais pourrait être d'autres noms) et de trouver la classe (par exemple 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 montrer la façon cross-compatible d'utiliser super
, mais si vous utilisez seulement Python 3, vous pouvez l'appeler sans arguments.
indirecte avec la Compatibilité ascendante
ça donne quoi? 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 de compatibilité indirecte avec forward.
la compatibilité Forward est très importante pour les développeurs chevronnés. Vous voulez que votre code continue à fonctionner avec des changements minimes 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 simple, 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 (par exemple un mixin est ajouté) vous ne changerez rien dans cette classe. En particulier dans Python 2, Obtenir les arguments à super
et les arguments de la méthode correcte peut être difficile. Si vous savez que vous utilisez super
correctement avec l'héritage simple, qui 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 méthode de résolution:
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__()
dites que vous ajoutez une autre classe à votre objet, et que vous voulez injecter une classe entre Foo et Bar (pour tester 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
en utilisant l'un-super child ne parvient pas à injecter la dépendance parce que l'enfant que vous utilisez A codé dur la méthode à appeler d'après la sienne:
>>> o = UnsuperInjector()
UnsuperChild.__init__(self) called
SomeBaseClass.__init__(self) called
Cependant, la classe avec l'enfant qui utilise super
peut injecter correctement la dépendance:
>>> o2 = SuperInjector()
SuperChild.__init__(self) called
InjectMe.__init__(self) called
SomeBaseClass.__init__(self) called
s'attaquer à un commentaire
pourquoi cela serait-il utile?
Python linéarise un arbre héréditaire compliqué via le C3 algorithme de linéarisation 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 de trouver le suivant, dans l'ordre, sans super
, il faudrait
- obtenir le mro à partir de l'instance du type
- rechercher le type qui définit la méthode
- trouver le type suivant avec la méthode
- lient cette méthode et l'appellent avec les arguments attendus
le
UnsuperChild
ne devrait pas avoir accès auInjectMe
. Pourquoi la conclusion N'est-elle pas "toujours éviter d'utilisersuper
"? Ce qui me manque ici?
Le UnsuperChild
ne pas ont accès à l' InjectMe
. C'est le UnsuperInjector
qui a accès à InjectMe
- et pourtant ne peut pas appeler la méthode de cette classe de la méthode qu'il hérite de UnsuperChild
.
les deux classes D'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 elle n'était pas au courant de quand elle a été créée.
celui sans super
les codes durs la méthode de ses parents - ainsi est a limité le comportement de son méthode, et les sous-classes ne peuvent pas injecter de fonctionnalité dans la chaîne d'appel.
celui avec 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 pouvez ne pas avoir besoin de cette fonctionnalité, mais les sous-classeurs de votre code peuvent.
Conclusion
toujours utiliser super
pour faire référence à la classe mère au lieu de le codage en dur.
ce que vous avez l'intention de faire est de faire référence à la classe de parent qui est la 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.
ne suppose-t-on pas que la classe de base est une nouvelle classe?
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 un nouveau style, I. e: class A(object)
j'avais joué un peu avec super()
, et j'avais reconnu qu'on pouvait 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 (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()
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
pour que nous puissions voir que l'ordre de résolution est même que de MRE. Mais quand nous appelons 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 MRO tuple.
A
/ ⇘
B ⇐ C
⇘ /
D
pour une lecture supplémentaire, je recommande les réponses suivantes:
en appelant super()
pour résoudre à la version d'un parent d'une méthode classmethod, instance, ou staticmethod, nous voulons passer la classe actuelle dont nous sommes dans la portée comme 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.
Envisager une hiérarchie de classe A
, B
, et C
où chaque class est le parent de celui qui le 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
utilisant super
avec une méthode statique
p.ex. utilisant super()
de la" méthode 1519250920
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-bien qu'il soit habituel pour __new__()
de prendre comme premier param une référence à l'appel classe, il est pas implémenté en Python comme une méthode de classe, mais plutôt une méthode statique. C'est-à-dire qu'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 obtenir à la classe de parent nous passons la classe de l'enfant A
comme son 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é passé quand A.__new__(cls)
a été appelé. Dans la plupart des cas, il arrive aussi d'être une référence à la classe enfant. Dans certaines situations, il se peut que ce ne soit pas le cas, par exemple dans le cas d'héritages de plusieurs générations.
super(A, cls)
3-puisqu'en règle générale __new__()
est une méthode statique, super(A, cls).__new__
retournera également une méthode statique et doit être fourni tous les arguments explicitement, y compris la référence à l'objet de l'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)
utilisant super
avec une méthode d'instance
p.ex. utilisant super()
de l'intérieur de __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'il est appelé directement de la exemple, la référence est passée implicitement, c'est que vous n'avez pas besoin de le préciser:
# 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 de l'enfant comme premier argument et l'objet de l'intérêt comme un deuxième argument, qui est en général une référence à une instance de la classe de l'enfant.
super(A, self)
3 - l'appel super(A, self)
renvoie un proxy qui résoudra la portée et l'appliquera à self
comme si c'est maintenant une instance de la classe parent. Appelons ce mandataire s
. Puisque __init__()
est une méthode d'instance, l'appel s.__init__(...)
passera implicitement une référence de self
comme premier argument au __init__()
du parent .
4 - pour faire la même chose sans super
nous devons passer 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)
utilisant super
avec classemethod
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 - Un classmethod peut être appelée à partir de la classe et prend en 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 vous appelez super()
dans une méthode de classe pour résoudre à la version de son parent de celui-ci, 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 de l'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 de l'enfant elle-même ou à l'une de ses sous-classes.
super(B, cls_or_subcls)
3-l'appel super(B, cls)
se résout au champ d'application 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 à A
la version 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 non lié version de A.alternate_constructor()
(c.-à-d. la version explicite de la fonction). Le simple fait de faire 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 la méthode A.alternate_constructor()
prend comme premier argument une référence implicite à A
. Le cls
étant 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(Child,self)
?
Lorsqu'une instance enfant est créée, son MRO(Method Resolution Order) est dans l'ordre de (Child, SomeBaseClass, object) basé sur 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
recherche dans le MRO de l'instance self
, et renvoie l'objet proxy next of Child, dans ce cas C'est SomeBaseClass, cet objet puis invoque la méthode __init__
de SomeBaseClass. En d'autres termes, si c'est super(SomeBaseClass,self)
, l'objet de procuration que super
retourne serait object
pour les héritages multiples, Le MRO peut contenir plusieurs classes, donc fondamentalement super
vous permet de décider où vous voulez commencer à chercher dans le MRO.