aspect-oriented demo using metaclasses
Pedro RODRIGUEZ
pedro_rodriguez at club-internet.fr
Sat Jun 29 15:27:52 EDT 2002
On Fri, 28 Jun 2002 17:31:06 +0200, Mark McEahern wrote:
<snip>
> I owe much to previous threads on AOP, in particular Pedro Rodriguez's
> non-metaclass implementation:
>
Mark,
If you really want something more consistent than my posting, I will
recommend you to also check:
- Pythius by Juergen Hermann
where Frank J. Tobin has introduced an aop module
http://pythius.sourceforge.net/ by Juergen Hermann
- Transwarp by Phillip J. Eby
this a more complex framework using in some way concepts of aop
http://telecommunity.com/TransWarp/
http://www.zope.org/Members/pje/Wikis/TransWarp/HomePage
<snipping introduction on metaclass>
>
> We want to iterate through the class's dict and do something for each
> method (I'm not worried about distinguishing staticmethod and
> classmethod type methods for now):
>
You should <wink>. At least for the purpose of the exercise ;)
(Honestly I didn't try either, it may end up with a two liner...
but I wonder if they will take two minutes or two hours to be written ;)
<sniping>
> Rather than importing types and using types.FunctionType, I just compare
> the type of the item to the type of an anonymous function (via lambda).
> Is that cool or what? ;-)
>
Even if deprecation of the types module have been discussed on the devel
list, I think it is preferable.
<sniping>
> Now we're starting to get somewhere! The next step is to create a
> framework where multiple observers can plug into the before and after
> events for any given method call. Rather than taking more baby steps to
> get there, this is the complete implementation I have so far:
>
You went even further than that. In the next step, you start separating
(at least ;) the aspect and the 'aspectified' object in a less intrusive
way.
> <code>
> import sys
>
> class wrapped_method(object):
>
> def __init__(self, cls, method):
> self.class_name = cls.__name__
> self.method = method
> self.method_name = method.func_name
> self.observers = []
>
> def __call__(self, *args, **kwargs):
> self.before(*args, **kwargs)
> self.method(self, *args, **kwargs)
> self.after(*args, **kwargs)
>
Oops... Didn't I make the same mistake...
... forgetting about the returned value ;)
Something like could be better:
def __call__(self, *args, **kwargs):
self.before(*args, **kwargs)
ret = self.method(self, *args, **kwargs)
self.after(*args, **kwargs)
return ret
<sniping>
> class Trace(Observer):
>
> def __init__(self, filename=None):
> self.filename = filename
>
> def write(self, prefix, class_name, method_name, *args, **kwargs):
> cls = class_name
> s = "%s: %s.%s(%s, %s)" % (prefix, cls, method_name, args,
> kwargs) if not self.filename:
> f = sys.stdout
> else:
> f = file(self.filename)
> f.write(s)
> f.write("\n")
> if self.filename:
> f.close()
>
> def before(self, class_name, method_name, *args, **kwargs):
> self.write("before", class_name, method_name, *args, **kwargs)
>
> def after(self, class_name, method_name, *args, **kwargs):
> self.write("after", class_name, method_name, *args, **kwargs)
>
This Trace class is an aspect.
> class Aspect(type):
>
> def __init__(cls, name, bases, dict):
> super(Aspect, cls).__init__(name, bases, dict) for k,v in
> dict.items():
> if type(v) == type(lambda x:x):
> setattr(cls, k, wrapped_method(cls, v))
>
This is not an Aspect. This is your way to implement method call interception.
<sniping>
> ************
> Observations
> ************
>
> Obviously, Trace is designed so that I can specify a filename when
> creating an instance of it and that would use that file rather than
> sys.stdout.
>
> Observers should probably be able to register for particular events
> (i.e., just before). They should be able to unregister. It should also
> be possible to register for some classes, some methods, and not others.
What about module functions ? Bounded methods ?
Using metaclass for call interception is too restrictive I believe.
> What is a good way to write the rules for enrolling in notifications?
> (And what's the word for this that AspectJ uses?)
Joinpoint... but you already found it ;)
>
> I explicitly pass self from the wrapped_method instance to the actual
> wrapped method. However, this is not really the instance of the class,
> it's the instance of the wrapped_method object. How do I pass the
> actual instance to the wrapped method--whether explicitly or implicitly?
>
This is the trickier part. When you do :
setattr(cls, k, wrapped_method(cls, v))
you substitute a function (actually an unbounded method) by a callable
object. Unfortunately when you'll invoke this object with a classical
method call, Python will not pass the instance as the first argument.
I only know two ways to substitute an unbounded method by an object
that will allow to retrieve the instance argument :
- providing a function, and take benifit of nested_scopes to retrieve
all the information from your context
- create an object through the 'new' package (new.instancemethod)
that was broken for new type objects in 2.2, and fixed in 2.2.1
So your wrapped_method class is not sufficient per se to achieve this
goal.
> I should probably explicitly set the metaclass for wrapped_method,
> Trace, Observer, and even VirtualClassError to "type" (the default
> metaclass) to avoid weird infinite loops where the members of the aspect
> framework are themselves being aspected.
>
I don't think that metaclass'ing is the good thing to do, but that's
just me.
> I need to think more about how this would be used. My vague impression
> of AspectJ is that you use it from an IDE, specifying different
> conditions for filtering classes and methods (what's the word for that
> again?). When you compile with aspect support turned on, the generated
> code has the aspects weaved in. What are the equivalent developer use
> cases for weaving aspects into Python code? The whole point it seems to
> me is that when I author the classes to be aspected, I shouldn't have to
> do a thing--no subclassing, no method definition. I just write my
> classes, omitting entirely any consideration of the future aspects to be
> weaved in. I mean, that's the whole point, right? To simplify the
> code.
>
Yep. And this is why I consider the metaclass thing for being too intrusive.
> In the example, foo doesn't know diddly about the fact that it's being
> traced. And we could turn that tracing on or off simply by changing the
> module-level __metaclass__ variable.
>
Too intrusive. I don't believe that you can do it dynamically, at least
not for classes defined at module level. They will be created with the
metaclass defined at compilation time.
<sniping>
> It would be easy to add aspects for error handling. This is a very
> quick and dirty example for error notification:
>
<snip>
Yes. Interception of raised exception is a good (and easy ;) feature.
Just try to go a step further with 'around' methods.
Thanks, for this posting Mark. Reminds me that aop is my task list for
the moment I will have some spare time (not so far I hope ;)
Just to give some hints on aop, this is what I wrote as a reminder
6 months ago along with a more complete implementation of aop :
Vocabulary
pointcut
an object identifying a set of joinpoints, and aggregating a set of
advices
joinpoint
a callable function in Python (unbound method, bound method, function)
advice
a function that will be called when a joinpoint is reached. It may be
triggered before the joinpoint is called, after, or when an exception
occurs.
An advice may also be set around a joinpoint, meaning that it will be
able to do some job before and after the joinpoint runs, but it will
have to call the joinpoint himself.
aspect
a class that contains poincuts and their related advices.
And a UML'ish model :
+-------------+ n +-------------+ 1 +-------------+
| PointCut | - - - -> | JoinPoint |-------->| Function +
+-------------+ +-------------+ +-------------+
| |
| |
V 1 |
+-------------+ n |
| AdviceStack |<-----------+
+-------------+
|
|
V n
+-------------+
| Advice |
+-------------+
- A pointcut uses several joinpoints
- Since a joinpoint may participate in several pointcuts, it will have
to trigger several set of advices (AdviceStack) related to each of the
pointcut it belongs to.
- A joinpoint is related to a Python function. It will intercept calls
to it.
+-------------+ 1 +-------------+
| JoinPoint |---------->| Function |
+-------------+ +-------------+
^ n
|
|
+-------------+
| CallableSet |
+-------------+
A
|
+-----------------+-------+---------+----------------+
| | | |
+--------------+ +-------------+ +-------------+ +-------------+
|ClassicalClass| | TypeClass | | Module | | Instance |
+--------------+ +-------------+ +-------------+ +-------------+
- A Python function related to a joinpoint belongs to a CallableSet.
- A CallableSet can be a classical python class, in which case the
function is an unbound method.
- A CallableSet can be a new python class, in which case the function
is an unbound method.
- A CallableSet can be a python instance, in which case the function
is a bound method.
- A CallableSet can be a python module, in which case the function
is a standard method.
Best regards,
Pedro
More information about the Python-list
mailing list