Je vois partout des exemples de méthodes que les super-classes devraient appeler:
super(SuperClass, instance).method(args)
Y a-t-il un inconvénient à faire:
SuperClass.method(instance, args)
Considérez la situation suivante:
class A(object):
def __init__(self):
print('Running A.__init__')
super(A,self).__init__()
class B(A):
def __init__(self):
print('Running B.__init__')
# super(B,self).__init__()
A.__init__(self)
class C(A):
def __init__(self):
print('Running C.__init__')
super(C,self).__init__()
class D(B,C):
def __init__(self):
print('Running D.__init__')
super(D,self).__init__()
foo=D()
Ainsi, les classes forment un soi-disant diamant d'héritage:
A
/ \
B C
\ /
D
L'exécution du code donne
Running D.__init__
Running B.__init__
Running A.__init__
C'est mauvais parce que le __init__
de C
est ignoré. La raison en est que le __init__
de B
appelle directement le __init__
de A
.
Le but de super
est de résoudre les diamants d'héritage . Si vous ne commentez pas
# super(B,self).__init__()
et commenter
A.__init__(self)
le code donne le résultat le plus souhaitable:
Running D.__init__
Running B.__init__
Running C.__init__
Running A.__init__
Maintenant, toutes les méthodes __init__
sont appelées. Notez qu'au moment où vous définissez B.__init__
vous pourriez pensez que super(B,self).__init__()
est la même chose que d'appeler A.__init__(self)
, mais vous vous trompez. Dans la situation ci-dessus, super(B,self).__init__()
appelle en fait C.__init__(self)
.
Holy smokes, B
ne sait rien de C
, et pourtant super(B,self)
sait appeler le __init__
de C
? La raison en est que self.__class__.mro()
contient C
. En d'autres termes, self
(ou ci-dessus, foo
) connaît C
.
Soyez donc prudent - les deux ne sont pas fongibles. Ils peuvent donner des résultats très différents.
Utiliser super
a des pièges. Il faut un niveau considérable de coordination entre toutes les classes du diagramme d'héritage. (Ils doivent, par exemple, avoir la même signature d'appel pour __init__
, car tout __init__
particulier ne saurait pas quel autre __init__
super
pourrait appeler ensuite, ou bien tilisez **kwargs
.) , vous devez être cohérent sur l'utilisation de super
partout. Ignorez-le une fois (comme dans l'exemple ci-dessus) et vous allez à l'encontre de l'objectif de super
. Voir le lien pour plus d'écueils.
Si vous avez un contrôle total sur votre hiérarchie de classe, ou si vous évitez les diamants d'héritage, il n'est pas nécessaire de super
.
Il n'y a pas de pénalité telle quelle, bien que votre exemple soit quelque peu erroné. Dans le premier exemple, il devrait être
super(SubClass, instance).method(args) # Sub, not SuperClass
et cela m'amène à citer les documents Python :
Il existe deux cas d'utilisation typiques pour
super
. Dans une hiérarchie de classes avec héritage unique,super
peut être utilisé pour faire référence aux classes parentes sans les nommer explicitement, rendant ainsi le code plus maintenable. Cette utilisation est étroitement parallèle à l'utilisation desuper
dans d'autres langages de programmation.Le deuxième cas d'utilisation consiste à prendre en charge l'héritage multiple coopératif dans un environnement d'exécution dynamique. Ce cas d'utilisation est unique à Python et ne se trouve pas dans les langages compilés statiquement ou les langages qui ne prennent en charge que l'héritage unique. Cela permet d'implémenter des "diagrammes en losanges" où plusieurs classes de base implémentent la même méthode Une bonne conception dicte que cette méthode a la même signature d'appel dans tous les cas (parce que l'ordre des appels est déterminé au moment de l'exécution, parce que cet ordre s'adapte aux changements dans la hiérarchie des classes, et parce que cet ordre peut inclure des classes frères inconnues avant Durée).
Fondamentalement, en utilisant la première méthode, vous n'avez pas à coder en dur votre classe parent pour les hiérarchies à classe unique, et vous ne pouvez tout simplement pas vraiment faire ce que vous voulez (efficacement/efficacement) en utilisant la deuxième méthode lorsque vous utilisez plusieurs héritage.