Welcome, guest | Sign In | My Account | Store | Cart

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 ;)

Python, 136 lines
  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()