This introduces a Python module which implements one essential concept in Aspect Oriented Programming (AOP).
Aspect Oriented Programming (AOP) extends Object Oriented Programming so that it allows the programmer to separate properties which are the same regardless of the classes they belong to. In AOP these properties are implemented only once in one place and then merged to the class structure everywhere they belong. For example, validating an SQL query and writing a log entry may be properties which do not need to know where they are executed --- so why write them all over the class structure?
In this document a light-weight solution to this problem is presented in Python. The solution is a function which takes an unbound method and another function (also known as advice) as its parameters. The function wraps the method inside the advice. Now when the method is called, the advice receives the control and it may, if it wishes, pass the control to the original method at some point of its execution.
The aspects module provides a function called wrap_around. The function takes two arguments:
wrap_around modifies the class of the unbound method so that when m is called, execution jumps actually to f. If self.__proceed method is called in f, the original method is executed.
In AOP terminology, advice f is applied to a pointcut in around fashion. The pointcut includes exactly one join point: the call of method m.
For example, consider class c:
class c: def foo(self): print "foo"
and function adv:
def adv(self, *args, **keyw): print "before" rv = self.__proceed(*args,**keyw) print "after" return rv
You can wrap the method foo of class c inside function adv by calling:
wrap_around( c.foo, adv )
Now, executing
o = c() o.foo()
results output
before foo after
When wrap_around is called with a method of a class and a function as its parameters, wrapping is implemented in the following way:
In the Python code of the previous section, the function calls caused by line o.foo() are:
c.foo # this is the new method created by wrap_around c.__wrap0foo # this is actually function "adv" c.__proceed # this function is called by "adv", the code is in # aspects.py c.__wrapped0foo # this is the original method "foo"
The implementation is in aspects.py
A tracer advice is defined in the tracer_advice.py module. The advice prints the name of the wrapped method, the class of the method and the class of the instance whose method is called. Then the advice passes control to the wrapped method and finally returns the return value of the wrapped method.
The tracer is used in the tracer_example.py module. This example demonstrates how the same advice can be used in several classes and around several methods. It also shows that a method can be wrapped in a derived class DD1 even if it is defined and already wrapped in its base class.
In this situation some attention should be paid to the order in which the methods are wrapped in the classes. If the method is wrapped first in the derived class and there are no wraps around methods of its base classes, then the proceed stack is created into the derived class. Now, if the same method of the base class is wrapped later on, the wrap in the base class will not be executed. Therefore, it is better to wrap first the methods of the base classes and then the methods of the derived classes.
Timeout advice generator create_timeout_advice is given in timeout_advice.py. The generator takes two optional parameters:
The generator returns a function that can be used as an advice in wrap_around calls.
Assume that timeout advice t is wrapped around method m. When m is called, t receives control. It initialises its timer and starts a new thread. The thread calls __proceed and thus passes control to the original method. When the method finishes, the thread saves the return value to a variable which can be accessed by the timeout advice.
In the meantime, the timeout advice waits for the thread to finish or time to run out. If time runs out, t returns None and leaves the thread running on its own. If the thread finishes before the timeout, t returns the value saved by the thread.
There are two examples where a timeout advice is used. First, timeout_example.py is a simple example that demonstrates how the timeout advice works when catching exceptions.
The second example is a more sophisticated, ``real-life'' program httpget.py. This program wraps timeout aspect around open method of URLopener class. The class is located in urllib which is a part of the standard library included in Python package. urllib provides high-level interface for fetching data from given URLs.
httpget takes URLs as command line parameters and prints their contents in standard output (stdout). If the contents cannot be read before timeout, which is as short as one second, nothing is printed in stdout. In addition, fetching every URL creates a result that is printed in standard error. The result contains information about the successfulness and duration of the operation.
What is remarkable in httpget is that the behaviour of the standard library is altered to match what is wanted without touching its code. Here the timeout code is merged inside the library: our httpget program calls library function urlopen, which uses URLopener class like before.
It has come to my attention that the timeout example does not work in Jython (thanks to Xavier Farrero for pointing this out).
aspects.py module implements one low-level functionality which helps to take a step towards AOP in Python. It does not provide a way to pack all aspect-related data in the same structure or any wildcards to define several join points in one pointcut like AspectJ does. But on the other hand, it does not introduce new syntax and it all can be done in a nice and dynamic Python way: while the program is running.
The Pythius project has produced utilities to assess the quality of Python code. The Pythius package also includes code for AOP in a much higher level than presented here.
However, there are some drawbacks in the Pythius's approach to AOP. For example, the classes to which aspects are applied must contain certain lines of code: the class defines exactly one aspect which is applied to it. In aspect oriented paradigm it would be more convenient to let the aspect define join points for its advices. This low-level implementation leaves this question open: you can define advices in one place and classes in another and bind them together in a third place. This makes modules that contain advices (such as tracer and timeout modules above) very reusable.
There are also other implementations:
All files mentioned above are downloadable in aspects.tar.gz package. The code is released under the GNU GPL General Public License.