1. coady
  2. multimethod


multimethod / multimethod.py

**Multiple argument dispatching**

Call *multimethod* on a variable number of types.
It returns a decorator which finds the multimethod of the same name, creating it if necessary, and adds that function to it.
For example::

    def func(*args):


*func* is now a multimethod which will delegate to the above function, when called with arguments of the specified types.
If an exact match can't be found, the next closest method is called (and cached).
If *strict* is enabled, and there are multiple candidate methods, a TypeError is raised.
A function can have more than one multimethod decorator.

See tests for more example usage.
Supported on Python 2.5 or higher, including Python 3.

import sys

class DispatchError(TypeError):

class signature(tuple):
    "A tuple of types that supports partial ordering."
    __slots__ = ()
    def __le__(self, other):
        return len(self) <= len(other) and all(map(issubclass, other, self))
    def __lt__(self, other):
        return self != other and self <= other
    def __sub__(self, other):
        "Return relative distances, assuming self >= other."
        return [list(left.__mro__).index(right) for left, right in zip(self, other)]

class multimethod(dict):
    "A callable directed acyclic graph of methods."
    def new(cls, name='', strict=False):
        "Explicitly create a new multimethod.  Assign to local name in order to use decorator."
        self = dict.__new__(cls)
        self.__name__, self.strict, self.cache = name, strict, {}
        return self
    def __new__(cls, *types):
        "Return a decorator which will add the function."
        namespace = sys._getframe(1).f_locals
        def decorator(func):
            if isinstance(func, cls):
                self, func = func, func.last
            elif func.__name__ in namespace:
                self = namespace[func.__name__]
                self = cls.new(func.__name__)
            self[types] = self.last = func
            return self
        return decorator
    def parents(self, types):
        "Find immediate parents of potential key."
        parents, ancestors = set(), set()
        for key, (value, superkeys) in self.items():
            if key < types:
                ancestors |= superkeys
        return parents - ancestors
    def __getitem__(self, types):
        return dict.__getitem__(self, types)[0]
    def __setitem__(self, types, func):
        types = signature(types)
        parents = self.parents(types)
        for key, (value, superkeys) in self.items():
            if types < key and (not parents or parents & superkeys):
                superkeys -= parents
        dict.__setitem__(self, types, (func, parents))
    def __delitem__(self, types):
        dict.__delitem__(self, types)
        for key, (value, superkeys) in self.items():
            if types in superkeys:
                dict.__setitem__(self, key, (value, self.parents(key)))
    def super(self, *types):
        "Return the next applicable method of given types."
        types = signature(types)
        keys = self.parents(types)
        if keys and (len(keys) == 1 or not self.strict):
            return self[min(keys, key=types.__sub__)]
        raise DispatchError("%s%s: %d methods found" % (self.__name__, types, len(keys)))
    def __call__(self, *args, **kwargs):
        "Resolve and dispatch to best method."
        types = tuple(map(type, args))
            func = self.cache[types]
        except KeyError:
            func = self.cache[types] = self[types] if types in self else self.super(*types)
        return func(*args, **kwargs)