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

Aspects can be used to add some extra behavior to functions or methods. This behavior is added at runtime and can be useful for things like logging, tracking references and even security.

Python, 117 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
# vim:sw=4:et

import sys

class Aspect(object):
    """Aspect defines the aspect interface that should be implemented by
    aspects.
    """

    def __init__(self, method):
        """This method takes the optional arguments given to the
        weave_method() function.
        """
        pass

    def before(self):
        """Code executed before the weaved method is executed.
        """
        pass

    def after(self, retval, exc):
        """Code executed after the weaved method has been executed.
        """
        pass

def weave_method(method, advice_class, *advice_args, **advice_kwargs):
    advice = advice_class(method, *advice_args, **advice_kwargs)
    advice_before = advice.before
    advice_after = advice.after

    def invoke_advice(*args, **kwargs):
        advice_before()
        try:
            retval = method(*args, **kwargs)
        except Exception, e:
            advice_after(None, e)
            raise
        else:
            advice_after(retval, None)
            return retval

    # Replace the method with our weaved one.
    try:
        class_ = method.im_class
    except:
        # The method is actually a simple function, wrap it in its namespace;
        method.func_globals[method.func_name] = invoke_advice
    else:
        #name = method.__name__
        setattr(class_, method.__name__, invoke_advice)
    return invoke_advice


class LoggerAspect(Aspect):

    def __init__(self, method):
        self.method = method

    def before(self):
        print 'entering', self.method

    def after(self, retval, exc):
        print 'leaving', self.method,
        if exc:
            print 'with exception %s(%s)' % (exc.__class__, exc)
        else:
            print 'with return value', retval

class ReferenceAspect(Aspect):
    """This reference keeps track of objects created by a method.
    A weak reference to those objects is created and appended to reflist.
    """

    def __init__(self, method, reflist):
        self.method = method
        self.reflist = reflist

    def after(self, retval, exc):
        import weakref
        if retval:
            self.reflist.append(weakref.ref(retval))


if __name__ == '__main__':

    def test_func(arg1, arg2):
        print 'test_func(): arg1:', arg1, ', arg2:', arg2

    weave_method(test_func, LoggerAspect)
    print 'testing test_func()'
    test_func('a', 'b')

    class TestClass(object):

        def test_meth(self, arg1, arg2):
            print 'TestClass.test_meth(): arg1:', arg1, ', arg2:', arg2

        def test_exc(self):
            raise ValueError, 'HELP'

    #print dir(TestClass.test_meth)
    #print TestClass.test_meth.im_class
    #print TestClass.test_meth.im_func
    #print TestClass.test_meth.im_self
    weave_method(TestClass.test_meth, LoggerAspect)
    weave_method(TestClass.test_exc, LoggerAspect)

    tc = TestClass()
    tc.test_meth(1, 2)
    try:
        tc.test_exc()
    except ValueError, e:
        print 'Caught expected exception', e
        import traceback
        traceback.print_exc()
    else:
        raise 'HUH'

I use this aspects module mainly for debugging. You can keep track of items using the ReferenceAspect (if you create your objects using some sort of factory). The LoggerAspect is handy if you want to know when a certain function or method is called.

2 comments

LJ Janowski 19 years, 5 months ago  # | flag

Question about aspects and 2.4 '@' function decorators. Very nice recipe. I'm not very familiar with aspect oriented development, but I can see this being useful almost immediately as a convenient approach to keeping debug information. One question, open to the world. I'm still trying to get my head around the new @ style decorators, but it seems like they might be useful when combined w/ this recipe. With the current approach, you have to weave every function for which you want to keep debug information. Still an improvement, but tedious if you are obsessive about sanity checking/logging and have lots of factory functions. In 2.4, shouldn't it be possible define a wrapper function that simply returns its argument, decorate any functions for which we want to keep debug information with this wrapper function, and then weave debug aspects on the wrapper? This would seem to be a painless way to manage debug/release versions of functions throughout a large program as this way functions don't have to know about a global debug variable to control their behavior and the debug version code is consolidated in one or two functions. Admittedly, this would defeat part of the point of function decorators (i.e. since you are going to weave in the aspect later, the function ends up having a 'decorated' behavior (via the aspect) assigned after the main function block). However, ideology aside, this should be fine as long as we make sure we follow this order: define the wrapper function first, the functions to be decorated next, and then finally apply the aspect to the wrapper function, shouldn't it?

Jonathan Wright 19 years, 5 months ago  # | flag

How about an unweave? Thanks for this recipe, this is really cool.

It looks like it wouldn't be too hard to also add an unweave function. This would allow debugging to be added and removed at runtime.

<dreaming>It would be rather nice to be able to add and remove logging from specific instances rather than specific classes... It's a real shame that python doesn't support instance methods.</dreaming>