Saturday 20 March 2010

A Crime Against Nature

Every so often, while writing Python, I've found myself wishing I could easily dispatch method calls according to the type of the arguments. The urge usually passes quickly, but... oh, the hell with it, there's no point trying to justify what I've done. Just look:

>>> import bondage
>>>
>>> class C(object):
... @bondage.discipline(int)
... def foo(self, arg):
... print 'int'
... @foo.discipline(str)
... def foo(self, arg):
... print 'str'
... @foo.discipline(int, str, int)
... def foo(self, arg1, arg2, arg3):
... print 'int, str, int'
...
>>> c = C()
>>> c.foo(1)
int
>>> c.foo('a')
str
>>> c.foo(1, 'a', 1)
int, str, int
>>> c.foo([])
Traceback (most recent call last):
File "", line 1, in <module>
File "bondage.py", line 18, in <lambda>
return lambda *args: self._dispatch(obj, *args)
File "bondage.py", line 22, in _dispatch
return self._argspecs[argspec](obj, *args)
KeyError: (<type 'list'>,)
>>>

I'd like to make it clear that there is absolutely no excuse for perpetrating this sort of insanity, ever. With that said, here's how I did it:

class discipline(object):

def __init__(self, *argspec):
self._argspecs = {}
self.discipline(*argspec)

def discipline(self, *argspec):
self._argspec = argspec
return self

def __call__(self, f):
self._argspecs[self._argspec] = f
return self

def __get__(self, obj, objtype=None):
return lambda *args: self._dispatch(obj, *args)

def _dispatch(self, obj, *args):
argspec = tuple(map(type, args))
return self._argspecs[argspec](obj, *args)

Obviously it's a stupid implementation, and if you wanted to do this properly you'd have to pay attention to subtypes, and do something clever with numeric types, and... oh, God, what am I saying?

Enough!

If you really want to do this "properly", use some other language where it's already built in, and begone.

1 comment:

Ville said...

Did you check "simplegeneric" module already?