Created
July 29, 2016 11:51
-
-
Save discort/95accba404b7fceb02b9f27655040561 to your computer and use it in GitHub Desktop.
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
"Multiple dispatch in Python with configurable dispatch resolution" | |
_AUTHOR=["David Mertz (mertz@gnosis.cx)",] | |
_THANKS_TO=[ | |
"Tim Hochberg (tim.hochberg@ieee.org)", | |
"Samuele Pedroni (pedronis@bluewin.ch)", | |
] | |
_COPYRIGHT=""" | |
This file is released to the public domain. I (dqm) would | |
appreciate it if you choose to keep derived works under terms | |
that promote freedom, but obviously am giving up any rights | |
to compel such. | |
""" | |
_HISTORY=""" | |
12/02 Initial "proof-of-concept" | |
01/03 Changed Dispatcher to Dispatch. | |
Changed 'next_meth' argument for Dispatch.add_rule() to | |
'propagate'. | |
Added a Dispatch.next_method() method that can be called | |
within a dispatched function (if the Dispatch instance | |
is passed in). | |
Provided example for using positional and keyword args. | |
02/03 Added a Dispatch.clone() method. Added additional | |
examples. | |
""" | |
#--- Constants (including Booleans if not defined in Python version) | |
AT_START = -1 # names for when to run other matching methods | |
SKIP = 0 | |
AT_END = 1 | |
if not globals().has_key('True'): True = 1 | |
if not globals().has_key('False'): False = 0 | |
#-- Sample functions linearize match tables in various ways | |
# 'signature' passed to each func is seq of classes in current call | |
# 'matches' is list of (sig,fn,nm) tups, already pruned for sig len/types | |
def def_order(signature, matches): | |
"Pass control according to first-defined-first-executed" | |
return matches | |
def reverse_def(signature, matches): | |
"Pass control according to first-defined-last-executed" | |
matches.reverse() | |
return matches | |
def unambiguous_lexicographic_mro(signature, matches): | |
"Use dispatch ranking similar to Dylan" | |
# I suppose this would need a topological sort | |
raise NotImplementedError, "Someone want to help?" | |
def lexicographic_mro(signature, matches): | |
"Use dispatch ranking similar to CLOS" | |
# Schwartzian transform to weight match sigs, left-to-right" | |
proximity = lambda klass, mro: mro.index(klass) | |
mros = [klass.mro() for klass in signature] | |
for (sig,func,nm),i in zip(matches,xrange(1000)): | |
matches[i] = (map(proximity, sig, mros), matches[i]) | |
matches.sort() | |
return map(lambda t:t[1], matches) | |
def weighted_mro(signature, matches): | |
"Use dispatch ranking similar to Perl Class::Multimethods" | |
# Schwartzian transform to weight match sigs, unbiased sum" | |
from operator import add | |
proximity = lambda klass, mro: mro.index(klass) | |
sum = lambda lst: reduce(add, lst) | |
mros = [klass.mro() for klass in signature] | |
for (sig,func,nm),i in zip(matches,xrange(1000)): | |
matches[i] = (sum(map(proximity,sig,mros)), matches[i]) | |
matches.sort() | |
return map(lambda t:t[1], matches) | |
#-- Dispatch class | |
class Dispatch(object): | |
def __init__(self, table=(), order=lexicographic_mro): | |
self.table = [] | |
for rule in table: | |
self.add_rule(*rule) | |
self.order = order | |
def clone(self): | |
"Create a new dispatcher (usually for a new thread)" | |
return Dispatch(self.table, self.order) | |
def __call__(self, *args): | |
self.results = [] | |
self.args = args | |
signature = [o.__class__ for o in args] | |
self.linearization = self.linearize_table(signature) | |
self.pos = 0 | |
self.run() | |
return self.results | |
def with_dispatch(self, *args): | |
return self(*(args+(self,))) | |
def run(self): | |
if self.pos >= len(self.linearization): | |
return | |
func, propagate = self.linearization[self.pos] | |
self.pos += 1 | |
if propagate < SKIP: self.run() | |
self.results.append(func(*self.args)) | |
if propagate > SKIP: self.run() | |
def add_rule(self, signature, func, propagate=SKIP): | |
self.table.append((signature, func, propagate)) | |
def add_dispatchable(self, signature, func, propagate=SKIP): | |
self.add_rule(signature+(Dispatch,), func, propagate) | |
def remove_rule(self, signature): | |
"Remove any rule with the given signature." | |
for s,f,nm in self.table: | |
if s == signature: self.table.remove((s,f,nm)) | |
def next_method(self): | |
func, _ = self.linearization[self.pos] | |
self.pos +=1 | |
result = func(*self.args) | |
self.pos -= 1 | |
return result | |
def linearize_table(self, sig): | |
from operator import mul | |
len_match = lambda (s,f,nm): len(s) == len(sig) | |
typ_match = lambda (s,f,nm): reduce(mul, map(issubclass, sig, s)) | |
all_match = lambda x: len_match(x) and typ_match(x) | |
table = filter(all_match, self.table) | |
if not table: | |
def nomatch(*a): | |
raise TypeError, \ | |
"%s instance: no defined call signature <%s>" %\ | |
(self.__class__.__name__, ",".join(map(type, sig))) | |
return [(nomatch,SKIP)] | |
return map(lambda l:l[1:], self.order(sig, table)) | |
def beats_game(): | |
class Thing(object): pass | |
class Rock(Thing): pass | |
class Paper(Thing): pass | |
class Scissors(Thing): pass | |
rock, paper, scissors = Rock(), Paper(), Scissors() | |
def true_func(*a): return True | |
def false_func(*a): return False | |
beats = Dispatch([((Rock, Scissors), true_func), | |
((Scissors, Paper), true_func), | |
((Thing, Thing), false_func), | |
((Paper, Rock), true_func), | |
]) | |
beats.order = weighted_mro | |
print "<rock, scissors> ", beats(rock, scissors) | |
print "<paper, scissors> ", beats(paper, scissors) | |
print "----- Add fire rule ------" | |
class Fire(Thing): pass | |
def firepower(a,b): return "Fire always wins!" | |
beats.add_rule((Fire, Thing), firepower, AT_END) | |
beats.add_rule((Thing, Fire), firepower, AT_END) | |
fire = Fire() | |
print "<fire, rock> ", beats(fire, rock) | |
print "<fire, paper> ", beats(fire, paper) | |
print "<fire, fire> ", beats(fire, fire) | |
print "<scissors, rock> ", beats(scissors, rock) | |
print "---- Remove fire rule ----" | |
beats.remove_rule((Fire, Thing)) | |
print "<fire, rock> ", beats(fire, rock) | |
print "<fire, paper> ", beats(fire, paper) | |
print "<scissors, rock> ", beats(scissors, rock) | |
#print beats(scissors, "") # Raises a TypeError | |
#for sig, func, propagate in beats.table: | |
# print map(lambda k: k.__name__, sig), | |
# print func.__name__, | |
# print propagate | |
def auto_dispatch(): | |
"Demonstrate dispatch propagation" | |
class General(object): pass | |
class Between(General): pass | |
class Specific(Between): pass | |
dispatch = Dispatch() | |
dispatch.add_rule((General,), lambda _:"General", AT_END) | |
dispatch.add_rule((Between,), lambda _:"Between", AT_END) | |
dispatch.add_rule((Specific,), lambda _:"Specific", AT_END) | |
print dispatch(General()) | |
print dispatch(Specific()) | |
def manual_dispatch(): | |
"Enable Dispatch.next_method() call within function bodies" | |
class General(object): pass | |
class Between(General): pass | |
class Specific(Between): pass | |
def do_general(x, dispatch): | |
print "start general" | |
return "-> general" | |
def do_between(x, dispatch): | |
print "start between" | |
print dispatch.next_method() | |
print "still between" | |
return "-> between" | |
def do_specific(x, dispatch): | |
print "start specific" | |
print dispatch.next_method() | |
print "still specific" | |
print dispatch.next_method() # same dispatch as prior call | |
return "-> specific" | |
# Either specify Dispatch in .add_rule(), or use .add_dipatchable() | |
multi = Dispatch() | |
multi.add_dispatchable((General,), do_general) | |
multi.add_dispatchable((Between,), do_between) | |
multi.add_rule((Specific, Dispatch), do_specific) | |
o = Specific() | |
x = multi.with_dispatch(o) | |
# Or: multi(o, multi) | |
print x | |
def function_args(): | |
"Call a multimethod with (simulated) positional or keyword arguments" | |
class Foo(object): pass | |
def say_args(foo, args=(), kw={}): | |
print "Arguments:", args | |
print " Keywords:", kw | |
print "-"*72 | |
multi = Dispatch() | |
multi.add_rule((Foo,), say_args) | |
multi.add_rule((Foo,tuple), say_args) | |
multi.add_rule((Foo,tuple,dict), say_args) | |
foo = Foo() | |
multi(foo) # no arguments | |
multi(foo, ('list','of','arguments')) | |
multi(foo, (), {'keyword':'arguments'}) | |
if __name__=='__main__': | |
#beats_game() | |
#auto_dispatch() | |
manual_dispatch() | |
#function_args() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment