From 'wrapping unbound' methods to 'aspect oriented' design

Pedro Rodriguez pedro_rodriguez at club-internet.fr
Sun Jan 13 01:11:14 CET 2002


Hello,

While trying to clean up some code I had, I noticed the following
problem : I had a class A with several methods m1, m2 ..., and
all those methods have the same "aspect" but not the best suited
to me. The code looked like :

    class A:
        ...
        def mx(self, ...):
            self.notify("start")
            ... mx code here ...
            self.notify("end")
        ...

and I wanted it to look like :

    class A:
        ...
        def mx(self, ...):
            try:
                self.notify("start")
                ... mx code here ...
                self.notify("end")
            except:
                self.notify("error")
        ...

There was several methods were I had to do the modification, and 
maybe this modification should also be applied to other classes :(
Probably there was a flow in my design, maybe there is something more
important I had missed while developping the first version.

I just happened to be looking at "aspect oriented" design (somebody
qualified this as something very promising in this newsgroup), and
I just realized that this was a typical example of what "aspects" are
targeted. 

You may check the following refs :
- http://aosd.net
- http://aspectj.org
- http://aspectj.org/doc/papersAndSlides

and I will recommand (the easier understand IMHO):
- AspectJ tutorial
- http://aspectj.org/documentation/papersAndSlides/ECOOP1997-AOP.pdf


So the idea is to separate the "aspect" from the actual code in A,
and find a way to wrap methods m1, m2,... so that they are used through
the "aspect". But there was a constraint, the "aspect" should not be
intrusive in A code. This means that I had to wrap A's methods from
"outside" and not in through A's code.

[OT : I will use 1.5.2 syntax : apply(f, args, kwargs) 
      instead of the more convenient 2.x : f(*args, **kwargs)
      ]


If wrapping a bound method is easy:

class Wrap:
    def __init__(self, boundMethod):
        self.boundMethod = boundMethod

    def __call__(self, *args, **kwargs):
        print "before"
        apply(self.boundMethod, args, kwargs)
        print "after"

class A:
    def __init__(self):
        self.f = Wrap(self.f)

    def f(self, x):
        print x

a = A()
a.f("Hello world")


wrapping an unbound method was trickier (btw, python 2.2 doesn't
seem to provide a way to subclass the types build by the "new" module
like "instancemethod" ?)

class A:
    def f(self, x):
        print x


import new

class Wrap:
    def __init__(self, cls, methodName):
        self.unboundMethod = getattr(cls, methodName)
        newMethod = new.instancemethod(self, None, cls)
        setattr(cls, methodName, newMethod)

    def __call__(self, *args, **kwargs):
        print "before"
        apply(self.unboundMethod, args, kwargs)
        print "after"


Wrap(A, "f")

a = A()
a.f("Hello world")


NOTICE : this is the simplest way I found to wrap existing code
without changing anything to the base class. With this kind of
idioms, I may add as many wrappers I want :

class Counter(Wrap):
    def __init__(self, cls, methodName):
        Wrap.__init__(self, cls, methodName)
        self.count = 0

    def __call__(self, *args, **kwargs):
        self.count = self.count + 1
        print "called %d time(s)" % self.count
        apply(self.unboundMethod, args, kwargs)

Counter(A, "f")
a.f("Hello World")


So the question is ... ? Am I overdoing ? 
I know there may be some issues :
- cascading aspects may lead to some unspected behaviour (order ?)
- exception handling
- ...

I hope some of you may also found this interesting.
I will shortly post an Aspect implementation.

-- 
Pedro
PS : In AspectJ they call the aspect functions "advice", 
     but maybe I am the one to "add vice" to my code ;)



More information about the Python-list mailing list