[Python-3000] Adaptation vs. Generic Functions

Guido van Rossum guido at python.org
Wed Apr 5 02:29:47 CEST 2006


#!/usr/bin/python2.4
"""An example of generic functions vs. adaptation.
After some face-to-face discussions with Alex I'm presenting here two
extremely simplified implementations of second-generation adaptation
and generic functions, in order to contrast and compare.
As the running example, we use the iterator protocol. Pretend that
the iter() built-in function doesn't exist; how would we implement
iteration using adaptation or generic functions?
"""
__metaclass__ = type # Use new-style classes everywhere
# 0. The SequenceIter class is shared by all versions.
class SequenceIter:
 def __init__(self, obj):
 self.obj = obj
 self.index = 0
 def next(self):
 i = self.index
 self.index += 1
 if i < len(self.obj):
 return self.obj[i]
 raise StopIteration
 def __iter__(self):
 # This exists so we can use this in a for loop
 return self
# 1. Do it manually. This is ugly but effective... until you need to
# iterate over a third party object type whose class you can't modify.
def ManualIter(obj):
 if isinstance(obj, (list, str, unicode)):
 return SequenceIter(obj)
 if isinstance(obj, dict):
 # We can't really do a better job without exposing PyDict_Next()
 return SequenceIter(obj.keys())
 if hasattr(obj, "__iter__"):
 return obj.__iter__()
 raise TypeError("Can't iterate over a %s object" % obj.__class__.__name__)
# 2. Using adaptation. First I show a simple implementation of
# adaptation. In a more realistic situation this would of course be
# imported. I call this "second generation adaptation" because the
# adaptation from protocol P to type T is invoked as P.adapt(T), and
# the registration of an adapter function A for type T is invoked as
# P.register(T, A). The only "smart" feature of this implementation
# is its support for inheritance (in T, not in P): if T has registered
# an adapter A for P, then A is also the default adapter for any
# subclass S of T, unless a more specific adapter is registered for S
# (or for some base of S that comes before T in S's MRO).
class Protocol:
 def __init__(self, name):
 self.registry = {}
 self.name = name
 def register(self, T, A):
 self.registry[T] = A
 def adapt(self, obj):
 for T in obj.__class__.__mro__:
 if T in self.registry:
 return self.registry[T](obj)
 raise TypeError("Can't adapt %s to %s" %
 (obj.__class__.__name__, self.name))
 def __call__(self, T):
 # This is invoked when a Protocol instance is used as a decorator.
 def helper(A):
 self.register(T, A)
 return A
 return helper
# Now I show how to define and register the various adapters. In a
# more realistic situation these don't all have to be in the same file
# of course.
AdaptingIterProtocol = Protocol("AdaptingIterProtocol")
@AdaptingIterProtocol(list)
def _AdaptingSequenceIter(obj):
 return SequenceIter(obj)
AdaptingIterProtocol.register(str, _AdaptingSequenceIter)
AdaptingIterProtocol.register(unicode, _AdaptingSequenceIter)
@AdaptingIterProtocol(dict)
def _AdaptingDictIter(obj):
 return SequenceIter(obj.keys())
@AdaptingIterProtocol(object)
def _AdaptingObjectIter(obj):
 if hasattr(obj, "__iter__"):
 return obj.__iter__()
 raise TypeError("Can't iterate over a %s object" % obj.__class__.__name__)
def AdaptingIter(obj):
 return AdaptingIterProtocol.adapt(obj)
# 3. Using generic functions. First I show a simple implementation of
# generic functions. In a more realistic situation this would of
# course be imported.
class GenericFunction:
 def __init__(self, default_function):
 self.default_function = default_function
 self.registry = {}
 def register(self, *args):
 def helper(F):
 self.registry[args] = F
 return F
 return helper
 def __call__(self, *args):
 types = tuple([obj.__class__ for obj in args])
 function = self.registry.get(types, self.default_function)
 return function(*args)
# Now I show how to define a generic function and how to register the
# various type-specific implementations. In a more realistic
# situation these don't all have to be in the same file of course.
@GenericFunction
def GenericIter(obj):
 """This is the docstring for the generic function."""
 # The body is the default implementation
 if hasattr(obj, "__iter__"):
 return obj.__iter__()
 raise TypeError("Can't iterate over %s object" % obj.__class__.__name__)
@GenericIter.register(list)
def _GenericSequenceIter(obj):
 return SequenceIter(obj)
GenericIter.register(str)(_GenericSequenceIter)
GenericIter.register(unicode)(_GenericSequenceIter)
@GenericIter.register(dict)
def _GenericDictIter(obj):
 return SequenceIter(obj.keys())
# 4. Show that all of these work equivalently.
def main():
 examples = [
 [1, 2, 3, 4, 5],
 "abcde",
 u"ABCDE",
 {"x": 1, "y": 2, "z": 3},
 (6, 7, 8, 9, 10), # Not registered, but has __iter__ method
 42, # Not registered and has no __iter__ method
 ]
 functions = [ManualIter, AdaptingIter, GenericIter]
 for function in functions:
 print
 print "***", function, "***"
 for example in examples:
 print ":::", repr(example), ":::"
 try:
 iterator = function(example)
 except Exception, err:
 print "!!! %s: %s !!!" % (err.__class__.__name__, err)
 else:
 for value in function(example):
 print repr(value),
 print
if __name__ == "__main__":
 main()
# --
# --Guido van Rossum (home page: http://www.python.org/~guido/)


More information about the Python-3000 mailing list

AltStyle によって変換されたページ (->オリジナル) /