From 'wrapping unbound' methods to 'aspect oriented' design (Aspect implementation)

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


Here is an implementation of a (primitive-) Aspect.
It is not a full featured thing like AspectJ, and looks quite
dumb in regard with aspectr (from Ruby), but I found its simplicity
quite "pythonic".

There may be gotchas regarding how to nest advices, and a possible
bug with Python 2.2 (new.instancemethod refuses a new class object
as the third argument - will submit a bug just in case...).

So the idea is :
- you create an aspect class by subclassing Aspect
- you write your advices with the following signature 
    def an_advice(self, method, methodName, inst, *args, **kwargs):
  where :
    - self is the aspect
    - method is the next advice to be called or the original method
    - methodName is the name of the aspect-ified method
    - inst is the object on which the method will be called
    - args, kwargs are the arguments to the real method
- "method" should only be used for "around" advices, since they MUST
  call your real method (or the next advice) : 
    apply(method, args, kwargs)
- "method" is useless for "before" and "after" advices
- you can then create an aspect to wrap any other class method with
    anAspect.wrap_around


# Aspect implementation -----------------------------------------------

import new

class Aspect:
    def wrap_around(self, adviceName, cls, methodName):
        adviceMethod = getattr(self, adviceName)
        AdviceAround(adviceMethod, cls, methodName)

    def wrap_before(self, adviceName, cls, methodName):
        adviceMethod = getattr(self, adviceName)
        AdviceBefore(adviceMethod, cls, methodName)

    def wrap_after(self, adviceName, cls, methodName):
        adviceMethod = getattr(self, adviceName)
        AdviceAfter(adviceMethod, cls, methodName)

class Advice:
    def __init__(self, adviceMethod, cls, methodName):
        self.methodName = methodName
        self.adviceMethod = adviceMethod
        self.origMethod = getattr(cls, methodName)
        newMethod = new.instancemethod(self.do_aspect, None, cls)
        setattr(cls, methodName, newMethod)

    def do_advice(self, *args, **kwargs):
        inst = args[0]
        apply(self.adviceMethod \
            , (self.origMethod, self.methodName, inst) + args
            , kwargs
            )
    
    def do_method(self, *args, **kwargs):
        apply(self.origMethod, args, kwargs)

    def do_aspect(self, *args, **kwargs):
        raise NotImplementedError

class AdviceAround(Advice):
    def do_aspect(self, *args, **kwargs):
        apply(self.do_advice, args, kwargs)

class AdviceBefore(Advice):
    def do_aspect(self, *args, **kwargs):
        apply(self.do_advice, args, kwargs)
        apply(self.do_method, args, kwargs)

class AdviceAfter(Advice):
    def do_aspect(self, *args, **kwargs):
        apply(self.do_method, args, kwargs)
        apply(self.do_advice, args, kwargs)

# Sample code ---------------------------------------------------------

class A:
    def f(self, x, y):
        print "real f %s %s" % (x, y)

    def g(self, x):
        print "real g %s" % x

class B:
    def z(self, *args, **kwargs):
        print "real z %s %s" % (str(args), str(kwargs))


class MyAspect(Aspect):
    def around_advice(self, method, methodName, inst, *args, **kwargs):
        print "start"
        apply(method, args, kwargs)
        print "stop"

    def before_advice(self, method, methodName, inst, *args, **kwargs):
        print ">>>>>>>>>"
        print "before", methodName, inst, args, kwargs

    def after_advice(self, method, methodName, inst, *args, **kwargs):
        print "after", methodName, inst, args, kwargs
        print "<<<<<<<<<"

aspect = MyAspect()
aspect.wrap_around("around_advice", A, "f")
aspect.wrap_before("before_advice", A, "g")

aspect.wrap_before("before_advice", B, "z")
aspect.wrap_after("after_advice", B, "z")


a = A()
a.f(1,2)
a.g(3)

print 

b = B()
b.z(1,2,3, x=42)
b.z(4)

-----------------------------------------------------------------------
This fails with Python 2.2 :
    ...
    newMethod = new.instancemethod(self.do_aspect, None, cls)
TypeError: instancemethod() argument 3 must be class, not type
-----------------------------------------------------------------------

class A(object):
    def f(self, x):
        print "real f", x

aspect.wrap_around("around_advice", A, "f")
a = A()
a.f(1)


-- 

Pedro



More information about the Python-list mailing list