Last active
June 8, 2017 14:34
-
-
Save spitz-dan-l/32cd681650ab7dfe84e1 to your computer and use it in GitHub Desktop.
Advanced python multiple dispatch implementation very advanced
This file contains hidden or 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
import random #definitely a good thing to see at the top of a multiple-dispatch implementation! | |
import abc | |
import itertools | |
class ArgValidator: | |
@classmethod | |
def validate(cls, *args, mro_distances=None): | |
if mro_distances is None: | |
mro_distances = [] | |
#disqualify validation rules with the wrong number of arguments | |
if len(args) != len(mro_distances): | |
return None | |
#return the euclidean distance from the match to the origin in mro space! | |
return sum(d*d for d in mro_distances)**0.5 | |
@classmethod | |
def mro_distance(cls, obj, C): | |
if issubclass(type(C), abc.ABCMeta): | |
virtual_mro = list(itertools.takewhile((lambda t: issubclass(t, C)), type(obj).__mro__)) | |
distance = len(virtual_mro) - 1 | |
if distance < 0: | |
distance = None | |
return distance | |
else: | |
try: | |
#number of levels up the mro chain for a match | |
return type(obj).__mro__.index(C) | |
except ValueError: | |
#disqualify if no match | |
return None | |
class Dispatcher: | |
def __init__(self): | |
self.impls = [] | |
def register(self, *arg_types): | |
validators = [] | |
for i, arg_type in enumerate(arg_types): | |
#dynamically create an ArgValidator subclass that validates whether a particular positional arg is an instance of a particular type! | |
ValidatorSubclass = type('{}_{}_validator'.format(arg_type.__name__, i), (ArgValidator,), {}) | |
#inner function creates and returns a closure in order to avoid creating pseudo-closure variables via default arguments because that would be an abomination! | |
#note that no purpose is served by lexically nesting create_validate_method() inside of register()! | |
def create_validate_method(pos, arg_type, V): | |
@classmethod | |
def validate(cls, *args, mro_distances=None): | |
if mro_distances is None: | |
mro_distances = [] | |
if pos >= len(args): | |
return None | |
mro_dist = cls.mro_distance(args[pos], arg_type) | |
if mro_dist is None: | |
return None | |
mro_distances.append(mro_dist) | |
#use cooperative multiple inheritance to determine whether other positional args validate! | |
return super(V, cls).validate(*args, mro_distances=mro_distances) | |
return validate | |
#monkey-patch dynamically-created subclass so that it has a reference to itself in its validate() method! | |
#(cooperative multiple inheritance doesn't work without the correct subclass passed as the first argument to super()!) | |
ValidatorSubclass.validate = create_validate_method(i, arg_type, ValidatorSubclass) | |
validators.append(ValidatorSubclass) | |
def wrapper(_impl): | |
#dynamically create a full validator class inheriting from multiple single-argument validator subclasses! | |
validator = type(_impl.__name__, tuple(validators), {}) | |
self.impls.append((validator, _impl)) | |
return _impl | |
return wrapper | |
def __call__(self, *args): | |
impl_distances = [] | |
impls = [] | |
#get the euclidean distance of all valid rules matching the input args! | |
for validator, impl in self.impls: | |
impl_dist = validator.validate(*args) | |
if impl_dist is not None: | |
impl_distances.append(impl_dist) | |
impls.append(impl) | |
#keep only the matching rules with lowest euclidean distance! | |
min_dist = min(impl_distances) | |
min_impls = [i for (d, i) in zip(impl_distances, impls) if min_dist == d] | |
if len(min_impls) == 1: | |
#no ambiguous match, go ahead and dispatch to the implementation! | |
return min_impls[0](*args) | |
elif len(min_impls) > 1: | |
#select one of the closest matches at random! | |
raise TypeError('Ambiguous dispatch') # return random.choice(min_impls)(*args) | |
raise NotImplementedError('Found no implementation for args {}'.format(', '.join(str(type(arg)) for arg in args))) | |
if __name__ == '__main__': | |
f = Dispatcher() | |
@f.register(int, int) | |
def _(a, b): | |
print('I got passed 2 ints') | |
@f.register(int, str) | |
def _(a, b): | |
print('I got passed an int and a str') | |
@f.register(int, int, str) | |
def _(a, b, c): | |
print('I got passed an int, an int and a str') | |
f(1, 2) | |
f(1, 'blarg') | |
f(1, 3, 'blarg') | |
class A: pass | |
class B(A): pass | |
@f.register(A, A) | |
def _(a1, a2): | |
print('I got passed 2 As') | |
@f.register(B, A) | |
def _(b1, b2): | |
print('I got passed a B and an A') | |
@f.register(A, B) | |
def _(a, b): | |
print('I got passed an A and a B') | |
f(A(), A()) | |
f(A(), B()) | |
f(B(), A()) | |
# for i in range(10): | |
# #different each time! | |
# f(B(), B()) | |
@f.register(B, B) | |
def _(b1, b2): | |
print('I got passed 2 Bs') | |
f(B(), B()) | |
class C(metaclass=abc.ABCMeta): | |
pass | |
C.register(A) | |
@f.register(C) | |
def _(c): | |
print('I got a C') | |
f(A()) | |
f(B()) | |
class D(metaclass=abc.ABCMeta): | |
pass | |
D.register(B) | |
@f.register(D) | |
def _(d): | |
print('I got a D') | |
f(A()) | |
f(B()) | |
class E(metaclass=abc.ABCMeta): | |
@classmethod | |
def __subclasshook__(cls, other): | |
return hasattr(other, 'e_member') | |
A.e_member = 1 | |
@f.register(E) | |
def _(e): | |
print('I got an E') | |
f(B()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is from when I decided to try programming as performance art. It is a really strange implementation of multiple dispatch (https://en.wikipedia.org/wiki/Multiple_dispatch) in python. It combines a number of esoteric and strange approaches to accomplish this. Among them are:
__mro__
attribute to identify the most-specific implementation to dispatch to.type
metaclass, combined with cooperative multiple inheritance and strategically chainedsuper()
calls to accomplish what afor
loop could accomplish.Do not use this for anything. Do not learn anything about programming from this.