[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