J'essaie de comprendre l'utilisation de super()
. De par son apparence, les deux classes enfants peuvent être créées, très bien.
Je suis curieux de connaître la différence entre les 2 classes d'enfants suivantes.
class Base(object):
def __init__(self):
print "Base created"
class ChildA(Base):
def __init__(self):
Base.__init__(self)
class ChildB(Base):
def __init__(self):
super(ChildB, self).__init__()
ChildA()
ChildB()
super()
vous permet d'éviter de faire explicitement référence à la classe de base, qui peut être Nice. Mais le principal avantage est l’héritage multiple, où toutes sortes de choses amusantes peuvent se produire. Voir le documentation standard sur super si vous ne l'avez pas déjà fait.
Notez que la syntaxe a été modifiée dans Python 3. : vous pouvez simplement dire super().__init__()
au lieu de super(ChildB, self).__init__()
lequel IMO est un peu plus agréable. La documentation standard fait également référence à un guide d'utilisation de super () , ce qui est assez explicatif.
J'essaie de comprendre
super()
La raison pour laquelle nous utilisons super
est que les classes enfants pouvant utiliser l'héritage multiple coopératif appellent la fonction de classe parent suivante correcte dans l'ordre de résolution de méthode (MRO).
Dans Python 3, on peut l'appeler ainsi:
class ChildB(Base):
def __init__(self):
super().__init__()
Dans Python 2, nous devons l’utiliser comme ceci:
super(ChildB, self).__init__()
Sans super, votre capacité à utiliser l'héritage multiple est limitée:
Base.__init__(self) # Avoid this.
J'explique plus en dessous.
"Quelle différence y a-t-il réellement dans ce code ?:"
class ChildA(Base):
def __init__(self):
Base.__init__(self)
class ChildB(Base):
def __init__(self):
super(ChildB, self).__init__()
# super().__init__() # you can call super like this in Python 3!
La principale différence dans ce code est que vous obtenez une couche d'indirection dans le __init__
avec super
, qui utilise la classe actuelle pour déterminer le __init__
de la classe suivante à rechercher dans le MRO.
J'illustre cette différence dans une réponse à la question canonique, Comment utiliser 'super' en Python? , qui illustre l'injection de dépendance et héritage multiple coopératif .
super
Voici un code qui est en fait très proche de super
(comment il est implémenté en C, moins certains comportements de vérification et de repli, et traduit en Python):
class ChildB(Base):
def __init__(self):
mro = type(self).mro() # Get the Method Resolution Order.
check_next = mro.index(ChildB) + 1 # Start looking after *this* class.
while check_next < len(mro):
next_class = mro[check_next]
if '__init__' in next_class.__dict__:
next_class.__init__(self)
break
check_next += 1
Écrit un peu plus comme Python natif:
class ChildB(Base):
def __init__(self):
mro = type(self).mro()
for next_class in mro[mro.index(ChildB) + 1:]: # slice to end
if hasattr(next_class, '__init__'):
next_class.__init__(self)
break
Si nous n'avions pas l'objet super
, nous devrions écrire ce code manuel partout (ou le recréer!) Pour nous assurer que nous appelons la méthode suivante appropriée dans l'ordre de résolution de méthode!
Comment super le fait-il dans Python 3 sans qu'on lui dise explicitement de quelle classe et de quelle instance de la méthode il a été appelé?
Il récupère le cadre de pile appelant et trouve la classe (implicitement stockée sous forme de variable libre locale, __class__
, transformant la fonction appelante en une fermeture sur la classe) et le premier argument de cette fonction, qui devrait être l'instance ou classe qui lui indique quel ordre de résolution de méthode (MRO) utiliser.
Puisqu'il nécessite ce premier argument pour le MRO, tiliser super
avec des méthodes statiques est impossible .
super () vous permet d'éviter de faire explicitement référence à la classe de base, qui peut être Nice. . Mais le principal avantage réside dans l'héritage multiple, où toutes sortes de choses amusantes peuvent se produire. Voir la documentation standard sur super si vous ne l'avez pas déjà fait.
C'est assez vague et ne nous dit pas grand chose, mais le but de super
n'est pas d'éviter d'écrire la classe parente. Le but est de s'assurer que la méthode suivante en ligne dans l'ordre de résolution de la méthode (MRO) est appelée. Cela devient important dans l'héritage multiple.
Je vais expliquer ici.
class Base(object):
def __init__(self):
print("Base init'ed")
class ChildA(Base):
def __init__(self):
print("ChildA init'ed")
Base.__init__(self)
class ChildB(Base):
def __init__(self):
print("ChildB init'ed")
super(ChildB, self).__init__()
Et créons une dépendance que nous voulons appeler après l'enfant:
class UserDependency(Base):
def __init__(self):
print("UserDependency init'ed")
super(UserDependency, self).__init__()
Maintenant, souvenez-vous que ChildB
utilise super, ChildA
ne:
class UserA(ChildA, UserDependency):
def __init__(self):
print("UserA init'ed")
super(UserA, self).__init__()
class UserB(ChildB, UserDependency):
def __init__(self):
print("UserB init'ed")
super(UserB, self).__init__()
Et UserA
n'appelle pas la méthode UserDependency:
>>> UserA()
UserA init'ed
ChildA init'ed
Base init'ed
<__main__.UserA object at 0x0000000003403BA8>
Mais UserB
, parce que ChildB
utilise super
, fait-il !:
>>> UserB()
UserB init'ed
ChildB init'ed
UserDependency init'ed
Base init'ed
<__main__.UserB object at 0x0000000003403438>
Vous ne devez en aucun cas effectuer les opérations suivantes, ce qu'une autre réponse suggère, car vous obtiendrez certainement des erreurs lorsque vous sous-classerez ChildB:
super(self.__class__, self).__init__() # Don't do this. Ever.
(Cette réponse n’est ni intelligente ni particulièrement intéressante, mais malgré les critiques directes dans les commentaires et plus de 17 votes défavorables, le répondeur a persisté à le suggérer jusqu’à ce qu’un gentil éditeur résolve son problème.)
Explication: Cette réponse a suggéré d'appeler super comme ceci:
super(self.__class__, self).__init__()
C'est complètement faux. super
nous permet de rechercher le prochain parent dans la MRO (voir la première section de cette réponse) pour les classes enfants. Si vous indiquez super
que nous sommes dans la méthode de l'instance enfant, la méthode suivante en ligne (probablement celle-ci) sera recherchée, ce qui entraînera probablement une récursivité, ce qui provoquera probablement un échec logique (dans l'exemple du répondeur) ou a RuntimeError
lorsque la profondeur de récursivité est dépassée.
>>> class Polygon(object):
... def __init__(self, id):
... self.id = id
...
>>> class Rectangle(Polygon):
... def __init__(self, id, width, height):
... super(self.__class__, self).__init__(id)
... self.shape = (width, height)
...
>>> class Square(Rectangle):
... pass
...
>>> Square('a', 10, 10)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in __init__
TypeError: __init__() missing 2 required positional arguments: 'width' and 'height'
Il a été noté que dans Python 3.0+, vous pouvez utiliser
super().__init__()
pour effectuer votre appel, ce qui est concis et ne vous oblige pas à référencer explicitement les noms de classe parent OR, ce qui peut être pratique. Je veux juste ajouter que pour Python 2.7 ou inférieur, il est possible d’obtenir ce comportement insensible au nom en écrivant self.__class__
au lieu du nom de la classe, c.-à-d.
super(self.__class__, self).__init__()
CEPENDANT, cela interrompt les appels à super
pour toutes les classes héritant de votre classe, où self.__class__
peut renvoyer une classe enfant. Par exemple:
class Polygon(object):
def __init__(self, id):
self.id = id
class Rectangle(Polygon):
def __init__(self, id, width, height):
super(self.__class__, self).__init__(id)
self.shape = (width, height)
class Square(Rectangle):
pass
Ici, j'ai une classe Square
, qui est une sous-classe de Rectangle
. Disons que je ne veux pas écrire un constructeur séparé pour Square
parce que le constructeur de Rectangle
est suffisant, mais pour une raison quelconque, je veux implémenter un Square afin de pouvoir réimplémenter une autre méthode.
Lorsque je crée un Square
à l'aide de mSquare = Square('a', 10,10)
, Python appelle le constructeur pour Rectangle
car je n'ai pas donné à Square
son propre constructeur. Cependant, dans le constructeur de Rectangle
, l'appel super(self.__class__,self)
va renvoyer la superclasse de mSquare
, de sorte qu'il appelle à nouveau le constructeur de Rectangle
. Voici comment se produit la boucle infinie, comme mentionné par @S_C. Dans ce cas, lorsque j'exécute super(...).__init__()
, j'appelle le constructeur pour Rectangle
, mais comme je ne lui donne aucun argument, j'obtiens une erreur.
Super n'a pas d'effets secondaires
Base = ChildB
Base()
fonctionne comme prévu
Base = ChildA
Base()
entre dans une récursion infinie.
Juste un avertissement ... avec Python 2.7, et je crois que depuis que super()
a été introduit dans la version 2.2, vous ne pouvez appeler que super()
si l'un des parents hérite d'une classe qui hérite finalement de object
( classes de style nouvea ).
Personnellement, comme pour le code python 2.7, je vais continuer à utiliser BaseClassName.__init__(self, args)
jusqu'à ce que je profite réellement de l'utilisation de super()
.
Il n'y en a pas vraiment. super()
regarde la classe suivante dans la MRO (ordre de résolution de la méthode, accessible avec cls.__mro__
) pour appeler les méthodes. Un simple appel de la base __init__
appelle la base __init__
. En l'occurrence, le MRO a exactement un élément: la base. Donc, vous faites vraiment exactement la même chose, mais de manière plus agréable avec super()
(particulièrement si vous entrez dans l'héritage multiple plus tard).
La principale différence est que ChildA.__init__
appellera inconditionnellement Base.__init__
alors que ChildB.__init__
appellera __init__
dans quelle que soit la classe qui serait ancêtre de ChildB
dans la ligne de self
ancêtres (qui peuvent différer de ce que vous attendez).
Si vous ajoutez un ClassC
qui utilise l'héritage multiple:
class Mixin(Base):
def __init__(self):
print "Mixin stuff"
super(Mixin, self).__init__()
class ChildC(ChildB, Mixin): # Mixin is now between ChildB and Base
pass
ChildC()
help(ChildC) # shows that the the Method Resolution Order is ChildC->ChildB->Mixin->Base
then Base
N'EST PLUS LE PARENT DE ChildB
pour les instances ChildC
. Maintenant, super(ChildB, self)
désignera Mixin
si self
est une instance ChildC
.
Vous avez inséré Mixin
entre ChildB
et Base
. Et vous pouvez en tirer parti avec super()
Ainsi, si vous avez conçu vos classes de sorte qu'elles puissent être utilisées dans un scénario Héritage multiple coopératif, vous utilisez super
car vous ne savez pas vraiment qui sera l'ancêtre à l'exécution.
Les super post super considéré et vidéo d'accompagnement de pycon 2015 expliquent assez bien cela.