Dynamic dispatch and inheritance in python
I'm trying to modify Guido's multimethod (dynamic dispatch code):
http://www.artima.com/weblogs/viewpost.jsp?thread=101605
to handle inheritance and possibly out of order arguments.
e.g. (inheritance problem)
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
Is there a better way than iteratively checking for the super() of each item until one is found?
e.g. (argument ordering problem) I was thinking of this from a collision detection standpoint.
e.g.
foo(Car(),Truck()) and
foo(Truck(), Car()) and
should both trigger
foo(Car,Truck) # Note: @multimethod(Truck,Car) will throw an exception if @multimethod(Car,Truck) was registered first?
I'm looking specifically for an 'elegant' solution. I know that I could just br开发者_如何学Cute force my way through all the possibilities, but I'm trying to avoid that. I just wanted to get some input/ideas before sitting down and pounding out a solution.
Thanks
Regarding the inheritance issue: This can be done with a slight change to MultiMethod. (Iterating through self.typemap and checking with 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
Note that self.typemap
is a dict, and dicts are unordered. So if you use @multimethod to register two functions, one whose types are subclasses of the other, then the behavior of foo
may be undefined. That is, the result would depend on which typemap_types
comes up first in the loop for typemap_types in self.typemap
.
super()
returns a proxy object, not the parent class (because you can have multiple inheritance), so that wouldn't work. Using isinstance()
is your best bet, although there's no way to make it as elegant as the dictionary lookups using type(arg)
.
I don't think allowing alternative argument orderings is a good idea; it's liable to lead to nasty surprises, and making it compatible with inheritance as well would be a significant headache. However, it would be quite simple to make a second decorator for "use this function if all the arguments are of type A", or "use this function if all the arguments are in types {A, B, E}".
精彩评论