Advicing Pything Functions

Date: 2013-09-13

Inspired by the defadvice mechanism in Emacs, which allows you to add some functionality to an already defined function. Such an advice contains code, which is executed every time before or after the original function is called. So your added functionality is executed without any further modifications in the existing source code. This is especially useful if you want to give the user a simple method to enable or disable a certain functionality without (big) impact on performance.

Because i needed this functionallity for a project i remembered these advices in emacs and implemented the behaviour in python, which was really straight-forward. I will just show a short example of the usage. The full source code is at [^2]. I will not do an overall source code discussion, if you have questions just put them in the comments.

[[!format py """ class CallLogAdvice(Advice): def before(self, args, kwargs): print "-> Args: %s, KWArgs: %s" %( args, kwargs) args[2] += 1 return (args, kwargs) def around(self, func, args, kwargs): print "-> Calling %s with %s, %s" % (func,args, kwargs) return func(args,kwargs) def after(self, ret): print "-> Return-value: %s" % ret return ret

@AdviceManager.advicable def func1(a, b ,c): print "In Function:",a, b, c return a + b + c

advice = CallLogAdvice("main.func1") func1(1,2,3) print advice.enable() func1(1,2,3) """]]

First the advice itself is defined. Every advice must inherit from the class Advice, which provides the mechanisms to register the advice at the AdviceManager. The advice has three user definable methods: before, around, and after. They are respectivly called in the life cycle of the function call. They can modify the arguments, wether the function is actually called and manipulate the return values.

Every function that should be advicable must be marked as so, because it is wrapped by the AdviceManager in a helper function to provide the advice funtionallity. Here func1 is marked advicable. Afterwards an advice is instanciated, which should advice func1. __main__ is needed here because i defined the function in the main-package. You have to give the package path to the advice (or the function object itself).

Afterwards the function is called two times. First withthe advice disabled secondly with the advice enabled:

In Function: 1 2 3

-> Args: [1, 2, 3], KWArgs: {}
-> Calling  with [1, 2, 4], {}
In Function: 1 2 4
-> Return-value: 7

As you can see the advice methods are called at the correct phases and can manipulate the arguments.

This might be a very useful tool for adding cross cutting constraints to existing code. It is also thinkable to mark all methods and functions as advicable (hint: metaclasses). I also want to mention two much more powerful concecpts which are closly related to such advices. The first is the common lisp object system CLOS and aspect-oriented programming (like in AspectC++[^3]). This are much more powerful tools for modeling cross-cutting constraints.