[Python-3000] Adaptation vs. Generic Functions

Phillip J. Eby pje at telecommunity.com
Fri Apr 7 08:31:15 CEST 2006


Guido wrote:
>         mros = tuple(t.__mro__ for t in types)
>         n = len(mros)
>         candidates = [sig for sig in self.registry
>                       if len(sig) == n and
>                          all(t in mro for t, mro in zip(sig, mros))]

I believe this can be simplified to:
     n = len(types)
     candidates = [sig for sig in self.registry
                   if len(sig) == n and
                      all(issubclass(s,t) for s,t in zip(sig, types))]


Similarly, I believe the dominates function can be moved out to a 
top-level function in similar fashion:

     def dominates(dom, sub):
         return all(d is not s and issubclass(d,s) for d,s in zip(dom, sub))

Using issubclass() is more accurate in this case than using __mro__, 
because __mro__ considers the order of the __bases__ to be 
significant, which can lead to unexpected results due to false 
precision.  (i.e., because it'll be guessing about cases that are 
actually ambiguous)

Unfortunately, this entire approach is subject to similar scale 
issues as PyProtocols/RuleDispatch.  It is much slower than a few 
hand-written if-then's for the common cases, and it's even slower and 
too inflexible for the "zillions of methods" cases like Zope's "view" 
registry (which dispatches based on some types and a string).  You 
could get better performance in the common cases by just creating a 
dominance hierarchy among all the methods, and then writing out 
Python code that executes a series of if-then tests with the dominant 
cases being tested first (and some extra branches to check for 
ambiguity).  This code would then run at virtually the same speed as 
an identical hand-written function for the most common use cases 
(which tend towards having only a handful of overloads at most), and 
it would also not require any extra memory allocations for temporary 
lists and tuples on each invocation of the function (because all the 
state is effectively tracked in the program counter as it threads its 
way among the if-then tests).

To make the same approach also work for the zillions-of-methods 
cases, all that would be needed is replacing some of the if-thens 
with dictionary lookups to allow N-way branches instead of just 
true/false tests, and some optimization to avoid re-testing 
conditions or recomputing values that have already been 
computed.  Most of RuleDispatch's complexity stems from the fact that 
I effectively implemented a mini-interpreter for executing such N-way 
dispatch trees, instead of just generating bytecode.  I made that 
choice because there's unfortunately no "computed goto" equivalent in 
today's bytecode.  (Bytecode is also rather annoyingly low-level for 
this kind of work.)





More information about the Python-3000 mailing list