Appeler la classe des parents init avec l'héritage multiple, Quelle est la bonne façon?
dites que j'ai un scénario d'héritage multiple:
class A(object):
# code for A here
class B(object):
# code for B here
class C(A, B):
def __init__(self):
# What's the right code to write here to ensure
# A.__init__ and B.__init__ get called?
il y a deux approches typiques à l'écriture C
's __init__
:
- (vieux style)
ParentClass.__init__(self)
- (plus récent de style)
super(DerivedClass, self).__init__()
cependant, dans les deux cas, si les classes de base ( A
et B
) ne suivent pas la même convention, alors le code ne sera pas travail correctement (certains peuvent manquer ou être appelé plusieurs fois).
alors, quelle est la bonne façon? Il est facile de dire "juste être cohérent, suivre l'un ou l'autre", mais si A
ou B
sont d'une bibliothèque tierce partie, Que faire alors? Y a-t-il une approche qui permet de s'assurer que tous les constructeurs de la classe mère sont appelés (et dans le bon ordre, et une seule fois)?
Edit: pour voir ce que je veux dire, si je fais:
class A(object):
def __init__(self):
print("Entering A")
super(A, self).__init__()
print("Leaving A")
class B(object):
def __init__(self):
print("Entering B")
super(B, self).__init__()
print("Leaving B")
class C(A, B):
def __init__(self):
print("Entering C")
A.__init__(self)
B.__init__(self)
print("Leaving C")
Puis-je obtenir:
Entering C
Entering A
Entering B
Leaving B
Leaving A
Entering B
Leaving B
Leaving C
notez que B
est appelé deux fois. Si je le fais:
class A(object):
def __init__(self):
print("Entering A")
print("Leaving A")
class B(object):
def __init__(self):
print("Entering B")
super(B, self).__init__()
print("Leaving B")
class C(A, B):
def __init__(self):
print("Entering C")
super(C, self).__init__()
print("Leaving C")
Puis-je obtenir:
Entering C
Entering A
Leaving A
Leaving C
notez que B
's init n'est jamais appelé. Il semble donc que, à moins de connaître ou de contrôler les entrées des classes dont j'hérite ( A
et B
), Je ne peux pas faire un choix sûr pour la classe que j'écris ( C
).
5 réponses
ça marche dans les deux sens. L'approche utilisant super()
conduit à une plus grande flexibilité pour les sous-classes.
dans l'approche par appel direct, C.__init__
peut appeler à la fois A.__init__
et B.__init__
.
en utilisant super()
, les classes doivent être conçues pour l'héritage multiple coopératif où C
appelle super
, qui invoque A
le code de "qui appellera aussi super
qui invoque B
le code de. Voir http://rhettinger.wordpress.com/2011/05/26/super-considered-super pour plus de détails sur ce qui peut être fait avec super
.
[question de réponse telle que modifiée ultérieurement]
il semble donc que si Je ne connais pas/ne contrôle pas les entrées des classes I hériter de (A et B) Je ne peux pas faire un choix sûr pour la classe que je suis l'écriture de (C).
l'article référencé montre comment gérer cette situation en ajoutant une classe d'emballage autour de A
et B
. Il y a un exemple élaboré dans la section intitulée "Comment incorporer une catégorie Non coopérative".
on pourrait souhaiter que les héritages multiples soient plus faciles, vous permettant de composer sans effort les classes de voiture et D'avion pour obtenir une voiture volant, mais la réalité est que les composants conçus séparément ont souvent besoin d'adaptateurs ou d'enveloppements avant de s'adapter ensemble aussi facilement que nous le ferions comme par exemple :-)
une autre pensée: si vous n'êtes pas satisfait de composer la fonctionnalité en utilisant l'héritage multiple, vous pouvez utiliser la composition pour le contrôle complet sur les méthodes qui sont appelées à quelles occasions.
la réponse à votre question dépend d'un aspect très important: vos classes de base sont-elles conçues pour l'héritage multiple?
il y a 3 scénarios différents:
-
les classes de base sont indépendantes et indépendantes.
si vos classes de base sont des entités séparées qui sont capables de fonctionner indépendamment et ils ne se connaissent pas, ils sont Non conçu pour l'héritage multiple.
Dans ce cas, vous devrez appeler chaque constructeur parent manuellement. C'est plus facile sans
super
.exemple:
class Foo: def __init__(self): self.foo = 'foo' class Bar: def __init__(self, bar): self.bar = bar class FooBar(Foo, Bar): def __init__(self, bar='bar'): Foo.__init__(self) # explicit calls without super Bar.__init__(self, bar) # To get the same results with `super`, you'd have to do this: # super().__init__() # super(Foo, self).__init__(bar) # Which is obviously much less intuitive.
Important: notez que ni
Foo
niBar
n'appellesuper().__init__()
! C'est pourquoi votre code n'a pas fonctionné correctement. En raison de la façon dont l'héritage de diamant fonctionne en python:- les classes dont la classe de base est
object
ne doivent pas appelersuper().__init__()
. Comme vous l'avez remarqué, cela briserait l'héritage multiple parce que vous finissez par appeler une autre classe__init__
plutôt queobject.__init__()
. - cela signifie également que vous devez ne jamais écrire une classe qui hérite de
object
et n'a pas de__init__
méthode . Pas de la définition d'un La méthode__init__
a le même effet que l'appelsuper().__init__()
. Si votre classe hérite directement deobject
, assurez-vous d'ajouter un constructeur vide comme ceci:
- les classes dont la classe de base est
.
class Base(object):
def __init__(self):
pass
-
l'une des classes est un mixin.
Un mixin est une classe conçu pour être utilisé avec plusieurs héritage. Cela signifie que nous n'avons pas à appeler les deux constructeurs parents manuellement, parce que le mixin appellera automatiquement le 2e constructeur pour nous. Puisque nous n'avons qu'à appeler un constructeur unique cette fois, nous pouvons le faire avec
super
pour éviter d'avoir à coder le nom de la classe mère.exemple:
class FooMixin: def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.foo = 'foo' class Bar: def __init__(self, bar): self.bar = bar class FooBar(FooMixin, Bar): def __init__(self, bar='bar'): super().__init__(bar) # a single call is enough to invoke # all parent constructors # NOTE: `FooMixin.__init__(self, bar)` would also work, but isn't # recommended because we don't want to hard-code the parent class.
les détails importants ici sont:
- le mixin appelle
super().__init__()
et passe par le biais de tous les arguments qu'il reçoit. - la sous-classe hérite du mélange première :
class FooBar(FooMixin, Bar)
. Si l'ordre des classes de base est erroné, le constructeur du mixin ne sera jamais appelé.
- le mixin appelle
-
toutes les classes de base sont conçues pour l'héritage coopératif.
les Classes conçues pour l'héritage coopératif ressemblent beaucoup à des mixins: elles passent à travers toutes les arguments pour la prochaine classe. Comme avant, il suffit d'appeler
super().__init__()
et tous les constructeurs parents seront appelés en chaîne.exemple:
class CoopFoo: def __init__(self, **kwargs): super().__init__(**kwargs) self.foo = 'foo' class CoopBar: def __init__(self, bar, **kwargs): super().__init__(**kwargs) self.bar = bar class CoopFooBar(CoopFoo, CoopBar): def __init__(self, bar='bar'): super().__init__(bar=bar) # pass all arguments on as keyword # arguments to avoid problems with # positional arguments and the order # of the parent classes
Dans ce cas, l'ordre des classes parent n'a pas d'importance. Nous pourrions ainsi hériter de
CoopBar
d'abord, et le code fonctionne encore le même. Mais c'est seulement vrai parce que tous les arguments sont passés comme des arguments de mots-clés. Utiliser des arguments de position rendrait facile d'obtenir le l'ordre des arguments est erroné, il est donc d'usage que les classes coopératives n'acceptent que les arguments par mots clés.il s'agit également d'une exception à la règle que j'ai mentionnée plus haut: les deux
CoopFoo
etCoopBar
héritent deobject
, mais ils appellent encoresuper().__init__()
. S'ils ne le faisaient pas, il n'y aurait pas d'héritage coopératif.
résultat: l'implémentation correcte dépend des classes dont vous héritez.
le constructeur fait partie de l'interface publique d'une classe. Si la classe est conçue comme un mixin ou pour un héritage coopératif, cela doit être documenté. Si les docs ne mentionnent rien de tel, il est raisonnable de supposer que la classe n'est pas conçu pour l'héritage multiple et vous devez appeler explicitement le constructeur de chaque classe mère (sans super
).
cet article Aide à expliquer l'héritage multiple coopératif:
http://www.artima.com/weblogs/viewpost.jsp?thread=281127
il mentionne la méthode utile mro()
qui vous montre l'ordre de résolution de la méthode. Dans votre deuxième exemple, où vous appelez super
dans A
, l'appel super
se poursuit dans MRO. La classe suivante dans l'ordre est B
, c'est pourquoi B
init est appelé pour la première fois.
voici un article plus technique du site officiel de python:
si vous multipliez les classes de sous-classe à partir de bibliothèques tierces, alors non, il n'y a pas d'approche aveugle pour appeler la classe de base __init__
méthodes (ou toute autre méthode) qui fonctionne réellement indépendamment de la façon dont les classes de base sont programmées.
super
rend possible d'écrire des classes conçues pour mettre en œuvre de manière coopérative des méthodes dans le cadre d'arbres complexes à héritages multiples qu'il n'est pas nécessaire que l'auteur de la classe connaisse. Mais il n'y a aucun moyen de l'utiliser pour hériter correctement de classes arbitraires qui peuvent ou ne peuvent pas utiliser super
.
essentiellement, si une classe est conçue pour être sous-classée en utilisant super
ou avec des appels directs à la classe de base est une propriété qui fait partie de la classe" "interface publique", et elle doit être documentée en tant que telle. Si vous utilisez des bibliothèques tierces de la manière que l'auteur de la bibliothèque attendait et la bibliothèque a une documentation raisonnable, il serait normalement vous dire ce que vous devez faire à la sous-classe des choses particulières. Si ce n'est pas le cas, alors vous devrez regarder le code source pour les classes que vous sous-classez et voir ce qu'est leur convention d'invocation de classe de base. Si vous combinez plusieurs classes d'une ou plusieurs bibliothèques tierces d'une manière que les auteurs de la bibliothèque n'attendaient pas , alors il ne sera peut-être pas possible d'invoquer systématiquement les méthodes de super-classe du tout ; si la classe a fait partie de une hiérarchie utilisant super
et la Classe B fait partie d'une hiérarchie qui n'utilise pas super, alors aucune des deux options n'est garantie de fonctionner. Tu devras trouver une stratégie qui fonctionne pour chaque cas particulier.
comme Raymond l'a dit dans sa réponse, un appel direct à A.__init__
et B.__init__
fonctionne bien, et votre code serait lisible.
Cependant, il n'utilise pas le lien d'héritage entre C
et ces classes. L'exploitation de ce lien vous donne plus de cohérence et rend les retouches éventuelles plus faciles et moins sujettes aux erreurs. Un exemple de la façon de faire cela:
class C(A, B):
def __init__(self):
print("entering c")
for base_class in C.__bases__: # (A, B)
base_class.__init__(self)
print("leaving c")