Clojure-styled multimethods can be easily added to Python using the Multiple Dispatch technique.
So, PEP-443 aka Single-dispatch generic functions has made it into Python. There is a nice writeup of the singledispatch
package features by Łukasz Langa.
Although I’m glad that Python is evolving in the right direction, I can’t see how single dispatch alone could be enough. In essence, PEP-443 defines a way of dynamically extending existing types with externally defined generic functions. Which is nice, but too limited.
What is really interesting is multiple dispatch. There are a few packages bringing multimethods to Python; all of them are overcomplicated to my taste.
Here’s my take on it. I will not talk much, better show you the code.
This is the complete implementation:
# multidispatch.py
import operator
from collections import OrderedDict
class DuplicateCondition(Exception): pass
class NoMatchingMethod(Exception): pass
class defmulti(object):
def __init__(self, predicate):
self.registry = OrderedDict()
self.predicate = predicate
def __call__(self, *args, **kw):
= self.dispatch(*args, **kw)
method return method(*args, **kw)
def dispatch(self, *args, **kw):
for condition, method in self.registry.items():
if self.predicate(condition, *args, **kw):
return method
return self.notfound
def notfound(self, *args, **kw):
raise NoMatchingMethod()
def when(self, condition):
if condition in self.registry:
raise DuplicateCondition()
def deco(fn):
self.registry[condition] = fn
return fn
return deco
def default(self, fn):
self.notfound = fn
return fn
@classmethod
def typedispatch(cls):
return cls(lambda type, first, *rest,
**kw: isinstance(first, type))
And here’s how to use it:
import types
from multidispatch import defmulti, NoMatchingMethod
# Exhibit A: Dispatch on the type of the first parameter.
# Equivalent to `singledispatch`.
= defmulti.typedispatch()
cupcakes
@cupcakes.when(types.StringType)
def str_cupcakes(ingredient):
return "Delicious {0} cupcakes".format(ingredient)
@cupcakes.when(types.IntType)
def int_cupcakes(number):
return "Integer cupcakes, anyone? I've got {0} of them.".format(number)
@cupcakes.default
def any_cupcakes(thing):
return ("You can make cupcakes out of ANYTHING! "
"Even out of {0}!").format(thing)
print cupcakes("bacon")
print cupcakes(4)
print cupcakes(cupcakes)
# Exhibit B: dispatch on the number of args, no default
@defmulti
def jolly(num, *args):
return len(args) == num
@jolly.when(1)
def single(a):
return "For {0}'s a jolly old fellow!".format(a)
@jolly.when(2)
def couple(a, b):
return "{0} and {1} are such a jolly couple!".format(a, b)
print jolly("Lukasz")
print jolly("Fish", "Chips")
try:
"Good", "Bad", "Ugly")
jolly(except NoMatchingMethod:
print "Noo! Angel Eyes!"