[RFC] Parametric Polymorphism
Bengt Richter
bokr at oz.net
Mon Sep 26 13:07:49 EDT 2005
On Sun, 25 Sep 2005 09:30:30 +0100, Catalin Marinas <catalin.marinas at gmail.com> wrote:
>Hi,
>
>Sorry if this was previously discussed but it's something I miss in
>Python. I get around this using isinstance() but it would be cleaner
>to have separate functions with the same name but different argument
>types. I think the idea gets quite close to the Lisp/CLOS
>implementation of methods.
>
>Below is just simple implementation example (and class functions are
>not supported) but it can be further extended/optimised/modified for
>better type detection like issubclass() etc. The idea is similar to
>the @accepts decorator:
>
>
>methods = dict()
>
>def method(*types):
> def build_method(f):
> assert len(types) == f.func_code.co_argcount
>
> if not f.func_name in methods:
> methods[f.func_name] = dict()
> methods[f.func_name][str(types)] = f
>
> def new_f(*args, **kwds):
> type_str = str(tuple([type(arg) for arg in args]))
> assert type_str in methods[f.func_name]
> return methods[f.func_name][type_str](*args, **kwds)
> new_f.func_name = f.func_name
>
> return new_f
>
> return build_method
>
>
>And its utilisation:
>
>@method(int)
>def test(arg):
> print 'int', arg
>
>@method(float)
>def test(arg):
> print 'float', arg
>
>test(1) # succeeds
>test(1.5) # succeeds
>test(1, 2) # assert fails
>test('aaa') # assert fails
>
>
>Let me know what you think. Thanks.
>
I am reminded of reinventing multimethods, but an interesting twist, so I'll add on ;-)
The following could be made more robust, but avoids a separately named dictionary
and lets the function name select a dedicated-to-the-function-name dictionary (subclass)
directly instead of looking it up centrally with two levels involved.
Just thought of this variant of your idea, so not tested beyond what you see ;-)
>>> def method(*types):
... def mkdisp(f):
... try: disp = eval(f.func_name)
... except NameError:
... class disp(dict):
... def __call__(self, *args):
... return self[tuple((type(arg) for arg in args))](*args)
... disp = disp()
... disp[types] = f
... return disp
... return mkdisp
...
>>> @method(int)
... def test(arg):
... print 'int', arg
...
>>> test
{(<type 'int'>,): <function test at 0x02EEAE2C>}
>>> @method(float)
... def test(arg):
... print 'float', arg
...
>>> test(1)
int 1
>>> test(1.5)
float 1.5
>>> test(1, 2)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 7, in __call__
KeyError: (<type 'int'>, <type 'int'>)
>>> test('aaa')
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 7, in __call__
KeyError: <type 'str'>
>>> test
{(<type 'int'>,): <function test at 0x02EEAE2C>, (<type 'float'>,): <function test at 0x02EEAE64>}
You could give it a nice __repr__ ...
Hm, I'll just cheat right here instead of putting it in the decorator's class where it belongs:
>>> def __repr__(self):
... fname = self.values()[0].func_name
... types = [tuple((t.__name__ for t in sig)) for sig in self.keys()]
... return '<%s-disp for args %s>' % (fname, repr(types)[1:-1])
...
>>> type(test).__repr__ = __repr__
>>> test
<test-disp for args ('int',), ('float',)>
>>> @method(str, str)
... def test(s1, s2): return s1, s2
...
>>> test
<test-disp for args ('int',), ('str', 'str'), ('float',)>
>>> test('ah', 'ha')
('ah', 'ha')
>>> test(123)
int 123
That __repr__ could definitely be improved some more ;-)
Regards,
Bengt Richter
More information about the Python-list
mailing list