#!/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/) _______________________________________________ Python-3000 mailing list Python-3000@python.org http://mail.python.org/mailman/listinfo/python-3000 Unsubscribe: http://mail.python.org/mailman/options/python-3000/archive%40mail-archive.com