Ian Bicking: the old part of his blog

More on Multimethods

I think Guide is thinking about typing and whatnot a lot lately, now with a post one multimethods, which mostly the same as double-dispatch. I.e., you choose a function implementation based on the type of the arguments.

However, Guido uses a global registry of functions by name. I like Phillip's technique for generic functions, which is to give these a functions a richer interface that includes a decorator. Here's an example of Guido's code converted to that:

def div(a, b):
    "div a by b"
    return a / b
class rational(object):
    def __init__(self, num, denom):
        self.num, self.denom = num, denom
    def __repr__(self):
        return '%s/%s' % (self.num, self.denom)
@div.types(rational, int)
def div_rat_int(r, i):
    return rational(r.num, r.denom*i)
@div.types(int, rational)
def div_int_rat(i, r):
    return rational(r.denom * i, r.num)
@div.types(rational, float)
def div_rat_float(r, f):
    return r.num / (r.denom*f)
@div.types(float, rational)
def div_float_rat(f, r):
    return f * r.denom / r.num
@div.types(rational, rational)
def div_rat_rat(r1, r2):
    return rational(r1.num * r2.denom, r1.denom * r2.num)

Here's how it looks:

>>> half = rational(1, 2)
>>> half
>>> third = rational(1, 3)
>>> div(half, third)
>>> div(half, 2)
>>> div(half, 0.5)
>>> div(1, 2)
>>> div(1, half)

And here's the implementation (it's actually simpler than Guido's):

class multimethod(object):
    def __init__(self, default):
        self.default = default
        self.name = default.__name__
        self.__doc__ = default.__doc__
        self.typemap = {}
    def __call__(self, *args):
        types = tuple(arg.__class__ for arg in args) # a generator expression!
        function = self.typemap.get(types, self.default)
        return function(*args)
    def types(self, *types):
        def decorator(func):
            if types in self.typemap:
                raise TypeError("duplicate registration")
            self.typemap[types] = func
            if func.__name__ == self.name:
                # to avoid confusion by overwriting the multimethod function
                return self
                return func
        return decorator

The module is located online at http://svn.colorstudy.com/home/ianb/recipes/multimethod.py

Update: some people have mistakenly called this implementation a generic function, or a reimplementation as a generic function. It's not generic (aka predicate-dispatch) functions at all -- those are much more general (and powerful), and this version is just a slightly different factoring of Guido's code. Thankfully Bob Ippolito does it right and shows how to implement multimethods with generic functions.

Created 01 Apr '05
Modified 04 Apr '05


Just an FYI, but if the user imports div "as" another name, this code won't catch that and return self. The technique I use in PyProtocols is to check whether the decorated function's __name__ in the caller's f_locals is self. So you could do something like:

if sys._getframe(1).f_locals.get(func.__name__) is self:
    return self
    return func

Then it doesn't depend on the original generic function's definition being only accessible via its original name, in order to prevent such confusion.

# Phillip J. Eby