From 'wrapping unbound' methods to 'aspect oriented' design
Pedro Rodriguez
pedro_rodriguez at club-internet.fr
Sat Jan 12 19:11:14 EST 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