2011-01-09 6 views
4

Je suis en train de modifier multiméthode Guido (code d'expédition dynamique):expédition dynamique et l'héritage en python

http://www.artima.com/weblogs/viewpost.jsp?thread=101605

pour gérer l'héritage et peut-être d'arguments d'ordre.

par exemple. (Problème d'héritage)

class A(object): 
    pass 

class B(A): 
    pass 

@multimethod(A,A) 
def foo(arg1,arg2): 
    print 'works' 


foo(A(),A()) #works 

foo(A(),B()) #fails 

Y at-il une meilleure façon de vérifier itérativement pour le super() de chaque élément jusqu'à ce qu'un se trouve?

par exemple. (problème d'ordre d'argument) Je pensais à ceci d'un point de vue de détection de collision.

par exemple.

foo(Car(),Truck()) and 
foo(Truck(), Car()) and 

devrait à la fois déclencher

foo(Car,Truck) # Note: @multimethod(Truck,Car) will throw an exception if @multimethod(Car,Truck) was registered first? 

Je suis à la recherche spécifiquement pour une solution 'élégante'. Je sais que je pourrais juste forcer mon chemin à travers toutes les possibilités, mais j'essaie d'éviter cela. Je voulais juste obtenir des commentaires/idées avant de m'asseoir et de trouver une solution.

Merci

+0

Vous pourriez jeter un oeil à PEAK-Rules qui est une autre implémentation de dispatch générique en python qui peut gérer l'héritage. http://pypi.python.org/pypi/PEAK-Rules – albertov

Répondre

2

super() retourne un objet proxy, et non pas la classe parente (parce que vous pouvez avoir l'héritage multiple), de sorte que ne fonctionnerait pas. Utiliser isinstance() est votre meilleur pari, bien qu'il n'y ait aucun moyen de le rendre aussi élégant que les recherches de dictionnaire en utilisant type(arg).

Je ne pense pas qu'autoriser des ordonnancements alternatifs soit une bonne idée; il est susceptible de conduire à de mauvaises surprises, et le rendre compatible avec l'héritage serait aussi un mal de tête important. Cependant, il serait assez simple de faire un second décorateur pour "utiliser cette fonction si tous les arguments sont de type A", ou "utiliser cette fonction si tous les arguments sont dans les types {A, B, E}".

2

En ce qui concerne le problème d'héritage: Cela peut être fait en modifiant légèrement MultiMethod. (Itère self.typemap et vérifier avec issubclass):

registry = {} 

class MultiMethod(object): 
    def __init__(self, name): 
     self.name = name 
     self.typemap = {} 
    def __call__(self, *args): 
     types = tuple(arg.__class__ for arg in args) # a generator expression! 
     for typemap_types in self.typemap: 
      if all(issubclass(arg_type,known_type) 
        for arg_type,known_type in zip(types,typemap_types)): 
       function = self.typemap.get(typemap_types) 
       return function(*args) 
     raise TypeError("no match") 
    def register(self, types, function): 
     if types in self.typemap: 
      raise TypeError("duplicate registration") 
     self.typemap[types] = function 

def multimethod(*types): 
    def register(function): 
     name = function.__name__ 
     mm = registry.get(name) 
     if mm is None: 
      mm = registry[name] = MultiMethod(name) 
     mm.register(types, function) 
     return mm 
    return register 

class A(object): 
    pass 

class B(A): 
    pass 

class C(object): 
    pass 

@multimethod(A,A) 
def foo(arg1,arg2): 
    print 'works' 


foo(A(),A()) #works 

foo(A(),B()) #works 

foo(C(),B()) #raises TypeError 

Notez que self.typemap est un dict et dicts ne sont pas ordonnés. Donc, si vous utilisez @multimethod pour enregistrer deux fonctions, l'une dont les types sont des sous-classes de l'autre, alors le comportement de foo peut être indéfini. Autrement dit, le résultat dépendra de ce qui typemap_types vient en premier dans la boucle.