This is the same as InverseExtend, except it uses a yield for non-local flow of control in a way yield wasn't really meant to be used ;)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | """Iterate downward through a hierarchy calling a method at each step."""
__docformat__ = "restructuredtext"
import types
def inverseExtend(boundMethod, *args, **kargs):
"""Iterate downward through a hierarchy calling a method at each step.
boundMethod -- This is the bound method of the object you're interested in.
args, kargs -- The arguments and keyword arguments to pass to the
top-level method.
You can call this method via something like this:
inverseExtend(object.method, myArg, myOtherArg)
When calling the method at each step, I'll call it like this:
Class.method(object, *args, **kargs)
Each parent class method *must* be a generator with exactly one yield
statement (even if the yield statement never actually gets called), but the
lowest level class method must *not* be a generator. In the parent class:
yield args, kargs
should be called when it is time to transfer control to the subclass. This
may be in the middle of the method or not at all if the parent class does
not wish for the child class's method to get a chance to run.
"""
# Build all the necessary data structures.
obj = boundMethod.im_self
methodName = boundMethod.im_func.__name__
# Figure out the classes in the class hierarchy. "classes" will
# contain the most senior classes first.
Class = obj.__class__
classes = [Class]
while Class.__bases__:
Class = Class.__bases__[0]
classes.insert(0, Class)
# Skip classes that don't define the method. Be careful with getattr
# since it automatically looks in parent classes.
last = None
methods = []
for Class in classes:
if (hasattr(Class, methodName) and
getattr(Class, methodName) != last):
last = getattr(Class, methodName)
methods.append(last)
# Traverse down the class hierarchy. Watch out for StopIteration's which
# signify that the parent does not wish to call the child class's method.
# generatorMethods maps generators to methods which we'll need for nice
# error messages.
generators = []
generatorMethods = {}
for method in methods[:-1]:
generator = method(obj, *args, **kargs)
assert isinstance(generator, types.GeneratorType), \
"%s must be a generator" % `method`
try:
(args, kargs) = generator.next()
except StopIteration:
break
generators.insert(0, generator)
generatorMethods[generator] = method
# If we didn't have to break, then the lowest level class's method gets to
# run.
else:
method = methods[-1]
ret = method(obj, *args, **kargs)
assert not isinstance(ret, types.GeneratorType), \
"%s must not be a generator" % method
# Traverse back up the class hierarchy. We should get StopIteration's at
# every step.
for generator in generators:
try:
generator.next()
raise AssertionError("%s has more than one yield statement" %
`generatorMethods[generator]`)
except StopIteration:
pass
# Test out the code.
if __name__ == "__main__":
from cStringIO import StringIO
class A:
def f(self, count):
buf.write('<A count="%s">\n' % count)
yield (count + 1,), {}
buf.write('</A>')
class B(A):
# I don't have an f method, so you can skip me.
pass
class C(B):
def f(self, count):
buf.write(' <C count="%s">\n' % count)
yield (count + 1,), {}
buf.write(' </C>\n')
class D(C):
def f(self, count):
buf.write(' <D count="%s" />\n' % count)
expected = """\
<A count="0">
<C count="1">
<D count="2" />
</C>
</A>"""
buf = StringIO()
d = D()
inverseExtend(d.f, 0)
assert buf.getvalue() == expected
buf.close()
|
Tags: oop