Multiple Dispatch in Python

Curtis Lusmore

I recently read Functional Programming in Python which mentions a library which adds multiple dispatch to Python. One of my biggest hobbies is creating things from scratch, as it really helps me get a better understanding of how they work, so I thought I’d see if I couldn’t figure out how to do it myself.

Here’s what I whipped up. I’ll add a disclaimer that in this form, it doesn’t work well for methods or class methods, as they both take a special argument in the first position (namely self or cls), and it doesn’t accept keyword arguments. At some stage I’ll try to make it a bit more rigorous.

class Dispatch:
    _existing = {}

    def __init__(self, *types):
        self.types = types

    def __call__(self, fn):
        name = fn.__qualname__
        if name not in self._existing:
            table = {}
            def dispatcher(*args):
                types = tuple(map(type, args))
                try:
                    overload = table[types]
                except KeyError:
                    try:
                        overload = table[(True,)]
                    except KeyError:
                        raise NotImplemented from None
                return overload(*args)
            self._existing[name] = (table, dispatcher)
        (table, dispatcher) = self._existing[name]
        table[self.types] = fn
        return dispatcher

Once you tuck this away in a library somewhere where you don’t have to look at it, you can use it to write some pretty elegant code. See the following implementation of the flatten function.

@Dispatch(list)
def flatten(elems):
    return reduce(add, map(flatten, elems), [])

@Dispatch(True)
def flatten(elem):
    return [elem]