Last active
February 21, 2017 07:41
-
-
Save NotBobTheBuilder/08bad9d2554fcde371fd5d7aae56c644 to your computer and use it in GitHub Desktop.
Python dynamic dispatch
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from collections import OrderedDict, namedtuple | |
from functools import partial | |
class Dispatcher(object): | |
def __init__(self): | |
self.handlers = OrderedDict() | |
def match(self, *args): | |
def match_inner(handler_fn): | |
self.handlers[args] = handler_fn | |
return self | |
return match_inner | |
def on(self, *args): | |
def on_inner(handler_fn): | |
# We need an outer closure here as a reference to `a` | |
# in a single closure would reference the last value of | |
# `a` after the for loop. | |
matchfn = lambda a: lambda v: isinstance(v, a) | |
matches = [matchfn(a) for a in args] | |
return self.match(*matches)(handler_fn) | |
return on_inner | |
def __call__(self, *args, **kwargs): | |
if len(args) == 0: | |
raise ValueError('No arguments to match on') | |
for matches, handler in self.handlers.iteritems(): | |
if any(m(args[-1]) for m in matches): | |
return handler(*args, **kwargs) | |
raise ValueError('No handlers found for value {}'.format(args[-1])) | |
def __get__(self, obj, objtype): | |
return partial(self.__call__, obj) | |
# Simple example: | |
foo = Dispatcher() | |
@foo.on(int, float) | |
def foo(x): | |
return "Got a number! {}".format(x) | |
@foo.on(str, unicode) | |
def foo(x): | |
return "Got some text! {}".format(x) | |
@foo.on(dict) | |
def foo(x): | |
return "Wow a dict {}".format(x) | |
print(foo(1)) | |
print(foo(1.0)) | |
print(foo('abc')) | |
print(foo(u'def')) | |
print(foo({})) | |
# Class Example: | |
Leaf = namedtuple('Leaf', 'l r') | |
tree = Leaf(Leaf(Leaf(1, 2), Leaf("3", 4.0)), [5]) | |
class TreeVisitor(object): | |
visit = Dispatcher() | |
@visit.on(Leaf) | |
def visit(self, value): | |
return self.visit(value.l) + ' || ' + self.visit(value.r) | |
@visit.on(int) | |
def visit(self, value): | |
return 'i' + str(value) | |
@visit.on(str) | |
def visit(self, value): | |
return 's"' + value + '"' | |
@visit.on(float) | |
def visit(self, value): | |
return 'f' + str(value) | |
@visit.on(list) | |
def visit(self, value): | |
return str(value) | |
print(TreeVisitor().visit(tree)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hat tip to Chris Lamb for the inspiration http://chris-lamb.co.uk/posts/visitor-pattern-in-python