[Python-3000] Generic functions
Ian Bicking
ianb at colorstudy.com
Tue Apr 4 17:27:57 CEST 2006
Walter Dörwald wrote:
> What happens, if I do the following
>
> @PrettyPrinter.pformat.when(object=list)
> def foo(...):
> ...
>
> @PrettyPrinter.pformat.when(object=object)
> def foo(...):
> ...
>
> How does it know which isinstance() check to do first?
It can see that isinstance(list, object) is true, and orders the checks
accordingly. If the condition is entirely ambiguous it raises an error.
At least, this is how RuleDispatch works. It has some knowledge of
operations like isinstance so that it can tell that the set of objects
that are instances of list is a strict subset of the objects that are
instances of object. Anyway, for the specific case of type-based
dispatch it's fairly straight forward to figure out what condition is
most specific. Though, say, if there's a specialized method for (A,
object) and a specialized method for (object, B) and you call with (A,
B) that's just ambiguous, and without an explicit resolution it should
be an error.
I guess another take on this, that precedes RuleDispatch, is multiple
dispatch, like in
http://www-128.ibm.com/developerworks/linux/library/l-pydisp.html -- the
article also precedes the decorator syntax I guess (or just doesn't use
it); I think the decorator syntax makes this look much nicer. Though
single-argument type-based dispatch is probably the most common case.
> And what happens with performance if I have registered many handler
> functions?
Depends on the implementation; I believe RuleDispatch builds a decision
tree.
>> [...]
>> The implementation of my simplistic form of generic function isn't too
>> hard. Ignoring keyword arguments, it might work like:
>>
>> class generic(object):
>> def __init__(self, func):
>> self.func = func
>> self.registry = {}
>> def __call__(self, *args):
>> for pattern, implementation in self.registry.items():
>> for passed, expected in zip(args, pattern):
>> # None is a wildcard here:
>> if (expected is not None and
>> not isinstance(passed, expected)):
>> break
>> else:
>> return implementation(*args)
>> return self.func(*args)
>> def when(self, *args):
>> def decorator(func):
>> self.registry[args] = func
>> return func
>> return decorator
>> def __get__(self, obj, type=None):
>> if obj is None:
>> return self
>> return types.MethodType(self, obj, type)
>>
>> There's lots of details, and handling keyword arguments, dealing
>> intelligently with subclasses, and other things I probably haven't
>> thought of. But anyway, this allows:
>>
>> class PrettyPrinter:
>> def pformat(self, object): ...
>>
>> # Without keyword arguments I have to give a wildcard for the self
>> # argument...
>> @PrettyPrinter.pformat(None, list)
>> def pformat_list(self, object):
>> ...
>
>
> I don't understand! There's no generic in sight here!
Maybe I should have called this more modest implementation simplistic
multiple dispatch. In this case it is dispatching on the second
argument (the first is "self" and not as interesting). pformat_list is
an implementation for objects of type list, PrettyPrinter.pformat is a
fallback implementation.
--
Ian Bicking / ianb at colorstudy.com / http://blog.ianbicking.org
More information about the Python-3000
mailing list