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

This is like the object oriented concept of extending a super class. However, instead of starting at the furthest subclass and working your way up, you start at the top super class and work your way down.

Python, 110 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
"""Iterate downward through a hierarchy calling a method at each step."""

__docformat__ = "restructuredtext"


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, callNext, *args, **kargs)

    However, the lowest level class's method has no callNext parameter,
    since it has no one else to call:

        Class.method(object, *args, **kargs)

    In the method:
    
        callNext(*args, **kargs) 
        
    should be called when it is time to transfer control to the subclass.  This
    may even be in the middle of the method.  Naturally, you don't have to pass
    *args, **kargs, but a common idiom is for the parent class to just receive
    *args and **kargs and pass them on unmodified.

    """

    # 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.insert(0, last)

    def callNext(*args, **kargs):
        """This closure is like super(), but it calls the subclass's method."""
        method = methods.pop()
        if len(methods):
            return method(obj, callNext, *args, **kargs)
        else:
            return method(obj, *args, **kargs)

    return callNext(*args, **kargs)


# Test out the code.
if __name__ == "__main__":

    from cStringIO import StringIO

    class A:
        def f(self, callNext, count):
            buf.write('<A count="%s">\n' % count)
            callNext(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, callNext, count):
            buf.write('  <C count="%s">\n' % count)
            callNext(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()

I wrote a "Dr. Dobb's Journal" article about this design pattern. The article has been accepted for publication, but it hasn't been published yet.