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.
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.
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?
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>