[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