2010-01-01 9 views
2

J'ai un objet de classe 'D' en python, et je veux exécuter séquentiellement la méthode 'run' telle que définie par 'D' et chacun de ses ancêtres ('A', ' B 'et' C ').Méthode d'appel sur chaque ancêtre en Python

Je suis en mesure d'y arriver comme ça

class A(object): 
    def run_all(self): 
     # I prefer to execute in revere MRO order 
     for cls in reversed(self.__class__.__mro__): 
      if hasattr(cls, 'run'): 
       # This works 
       cls.run(self) 
       # This doesn't 
       #cls.__getattribute__(self, 'run')() 

    def run(self): 
     print "Running A" 

class B(A): 
    def run(self): 
     print "Running B" 

class C(A): 
    def run(self): 
     print "Running C" 

class D(C, B): 
    def run(self): 
     print "Running D" 

if __name__ == "__main__": 
    D().run_all() 

qui se traduit par

$ python test.py 
Running A 
Running B 
Running C 
Running D 

Cependant, dans la pratique, je ne veux pas connaître le nom de la méthode à exécuter. Mais si j'essaie cela en utilisant getattribute() (voir commentaires) ligne ne fonctionne pas:

$ python test.py 
Running D 
Running D 
Running D 
Running D 

Mes questions sont les suivantes:

  1. Pourquoi ça ne marche pas?

  2. Est-ce encore la meilleure façon de procéder?

Répondre

1

Vous ne devriez pas utiliser la méthode __getattribute__ ..

juste faire ce qui suit:

getattr(cls, 'run')(self) 
+1

vous pouvez utiliser __getattribute__, mais il aura l'air très "pas sympa" - cls .__ getattribute __ (cls, 'run') (auto) –

1

Pourquoi ne pas utiliser simplement super? Bien que certains consider it harmful, il a été conçu avec exactement ce genre de scénario à l'esprit, et je voudrais l'utiliser sans aucune hésitation.

De Python documentation:

This is useful for accessing inherited methods that have been overridden in a class. The search order is same as that used by getattr() except that the type itself is skipped. [...] This makes it possible to implement “diamond diagrams” where multiple base classes implement the same method.

Mise à jour: Dans votre cas, il deviendrait quelque chose comme ceci:

class A(object): 

    def run(self): 
     print "Running A" 

class B(A): 
    def run(self): 
     super(B, self).run() 
     print "Running B" 

class C(A): 
    def run(self): 
     super(C, self).run() 
     print "Running C" 

class D(C, B): 
    def run(self): 
     super(D, self).run() 
     print "Running D" 

if __name__ == "__main__": 
    D().run() 
+0

J'ai essayé ça à l'origine, mais j'ai eu des problèmes avec super (self .__ class__, self) (toujours semble renvoyer self .__ class__, au moins dans la situation ci-dessus) et je ne veux pas que l'utilisateur doive l'appeler à chaque fois. –

+0

Oui, 'super' requiert une spécification explicite de la classe, comme dans la première suggestion de code de ma réponse - l'utilisation de' self .__ class__' ne peut pas fonctionner correctement. (Python 3 est un peu mieux comme ça, btw). –

+0

Pourquoi n'utilisez-vous pas directement super (D, self) .run()? Je mets un exemple dans la réponse principale. –

4

Si vous êtes OK avec changement de toutes les implémentations run (et appelant run au lieu de run_all en D), cela fonctionne:

class A(object): 
    def run(self): 
     print "Running A" 

class B(A): 
    def run(self): 
     super(B, self).run() 
     print "Running B" 

class C(A): 
    def run(self): 
     super(C, self).run() 
     print "Running C" 

class D(C, B): 
    def run(self): 
     super(D, self).run() 
     print "Running D" 

if __name__ == "__main__": 
    D().run() 

Notez que je ne suis pas utilisation super dans la classe racine - il « sait » il n'y a pas superclasse plus aller jusqu'à (object ne définit pas une méthode run). Malheureusement, dans Python 2, c'est inévitablement verbeux (et pas bien adapté à l'implémentation via un décorateur, soit).

Votre chèque sur hasattr est assez fragile, si je comprends bien vos objectifs - il trouvera qu'une classe « a » l'attribut si elle définit ou hérite il. Donc, si vous avez une classe intermédiaire qui ne remplace pas run mais se produit sur le __mro__, la version de run héritée est appelée deux fois dans votre approche. Par exemple., Tenez compte:

class A(object): 
    def run_all(self): 
     for cls in reversed(self.__class__.__mro__): 
      if hasattr(cls, 'run'): 
       getattr(cls, 'run')(self) 
    def run(self): 
     print "Running A" 
class B(A): pass 
class C(A): 
    def run(self): 
     print "Running C" 
class D(C, B): pass 

if __name__ == "__main__": 
    D().run_all() 

cette imprime

Running A 
Running A 
Running C 
Running C 

avec deux "bégaiements" pour les versions de run qui B et D Hériter sans écraser (de A et C respectivement). En supposant que je ne me trompe pas que ce soit pas l'effet que vous voulez, si vous êtes désireux d'éviter super vous pouvez essayer de changer run_all à:

def run_all(self): 
    for cls in reversed(self.__class__.__mro__): 
     meth = cls.__dict__.get('run') 
     if meth is not None: meth(self) 

qui, substitué dans mon dernier exemple avec seulement deux def distincts s pour run dans A et C, fait l'impression par exemple:

Running A 
Running C 

que je pense peut-être plus proche de ce que vous voulez. Un autre côté: ne répétez pas le travail - getattr guarding getattr, ou un in test gardant l'accès dict - à la fois la vérification dans la garde, et l'accesseur gardé, doit répéter exactement le même travail en interne, pas de bon but. Au lieu de cela, utilisez un troisième argument de None pour un seul appel getattr (ou la méthode get du dict): cela signifie que si la méthode est absente, vous récupérerez une valeur None, et vous pourrez alors garder l'appel contre cela occurrence. C'est exactement la raison pour laquelle les dicts ont une méthode get et getattr a un troisième argument "default" optionnel: pour faciliter l'application de DRY, "ne vous répétez pas", une maxime très importante de bonne programmation! -)

+0

C'est exactement ce que je cherchais, et vous avez raison sur les "bégaiements", je n'avais pas pensé à ça. Merci tout le monde! –

+0

Aussi, si je comprends bien, pour utiliser super avec héritage multiple vous devez l'appeler de la classe 'racine' afin de traverser l'arbre/diamant/tout (comme Roberto l'a fait plus bas), même si je peux me tromper cette. –

+0

Ah, je vois ce que tu veux dire. Je me suis trompé. –