aspect-oriented demo using metaclasses

Pedro Rodriguez pedro_rodriguez at club-internet.fr
Sun Jun 30 17:12:13 EDT 2002


[Mark]
> The latest version I posted continues to punt on staticmethod and
> classmethod.  They're currently not aspected because my type comparison
> to type(lambda x:x) will effectively filter out staticmethod and
> classmethod. I would have to change the observer interface slightly
> since staticmethods don't have have a self parameter and the first
> parameter of classmethods is a reference to the class not the instance.
> I don't see this being too hard--I plan to fix it in the next iteration.
> 
>> Even if deprecation of the types module have been discussed on the
>> devel list, I think it is preferable.
> 
> Yeah, using type(lambda x:x) seems more portable or something.
> 

Sorry, but I wasn't clear : I meant that using the types module should
be better. But honnestly I am not that sure.


[Mark]
> Hmm, you're right, my approach will do NOTHING for methods that aren't
> associated with a class.  BTW, I thought a normal method associated with
> a class whether new type or old was a bound method?  I'm not too worried
> about old style classes--although maybe I should be?  I figure if a
> requirement of using this is that it only works with new-style classes,
> that's fine with me.  Until I run into a problem, of course.  ;-)
> 

Just to be sure, let me clarify some points about functions and methods :
- a def statement introduces a function
- when a function is declare in a class we should talk of an unbound 
  method
- when we make a reference to a function from an instance we talk of a
  bounded method

>>> def f(x): pass
...
>>> class A:
...     def f(self): pass
...
>>> a = A()
>>>
>>> print f
<function f at 0x8150854>
>>> print A.f
<unbound method A.f>
>>> print a.f
<bound method A.f of <__main__.A instance at 0x8150824>>
>>>

The difference between an unbound method and a bound method is that the
latest won't need to be passed a 'self' argument because it is already
bounded to an object.

For class and static methods, I will need some time to learn how to
identify them and catalog them in my 'bestiaire'.


[Mark]
> I often use methods that aren't associated with a class and I'd like to
> aspect those as well, although, when I think about something like
> Persistence, that does seem initimately bound up with classes.  So maybe
> it depends on the aspect?

I see your points here. 

Could/Should an aspect be used for Persistence ?  I don't know, but it is 
a good remark. 

I recall that I discovered AOP after having to add some notification in
Models in an Model/View/Presenter framework and it seemed quite intrusive.

With your remark I wonder if the saving of my models (call it
Persistence/Serialization) should also be considered as a good candidate
for being aspectified. By doing so that I could keep my model simple and
externalize the way I want to implement save/load.


> 
> I should say that my motivation for pursuing a metaclass implementation
> is partly just to learn metaclasses, 

Understanding metaclasses is quite a challenge, putting them to good use
is, IMO, even more challenging (Alex Martelli recommended the reading of
the book mentionned in the 'descintro' document :
    Putting Metaclasses to Work: A New Dimension in Object-Oriented Programming,
    by Ira R. Forman and Scott H. Danforth)


> ... but also partly because I want
> something that requires no modification to the aspected class and
> minimal effort, maximum flexibility for defining join points.  What I
> like about the metaclass approach is that I just wire up every single
> class that's aspected for all events.  It's then up to the observer to
> hook into them.  Of course, I need to try to use this for something real
> and I'm sure that will expose its weaknesses/my conceptual gaps quickly.
> 

I think you should clearly separate :
1. the place where you do your 'method call interception'
2. the way you stack (and call) advices

Actually you do 1. at class definition level for all methods defined in
the class by using a metaclass, and for 2. you use an observer-like
object.

For 1. I decided to it in a different way, closer to Java implementations
(and I think that aop.py in Pythius already did it this way) :
- I created a Joinpoint class
- the constructor received an object called a callableSet and a string
  pattern
- a callableSet was anything that could contain a 'function' I wanted to
  intercept. This could be a module, a class, an instance
- I scaned the callableSet in search for attributes that were functions
  that matched the pattern
- I intercepted the matching functions with what I defined for 2.


[Pedro]
>> 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.
[Mark]
> 
> This is a good point.  Here's the sort of dynamism I'm aiming for:
> 
> The modules containing classes to be aspected have no reference to the
> framework inside them.  Not even a __metaclass__ statement.
> 
> Oops, a little testing shows that may simply not work.  Hmm.  I don't
> want to have to edit the modules that contain the classes to be
> aspected, even to add a __metaclass__ declaration.  Since I can't seem
> to change that at runtime, this approach probably won't work.
> 

This is what I feared with the usage of metaclasses (at least by solely
using __metaclass__ in this way)


[Pedro]
>> Yes. Interception of raised exception is a good (and easy ;) feature.
>> Just try to go a step further with 'around' methods.
[Mark]
> 
> I avoided adding around because it doesn't seem primitive to me.  In
> other words, isn't around just before + after notification?  So I could
> add support for around simply by making it so that observers wanting
> around notification just got before and after notification?  Not sure
> what to do if the method raises an error--skip the after?
> 
No 'around' is not that simple I fear. I think you could make some
comparison with 'generators'. One of the purpose of generators is to
simplify design by not having to keep a context between invocations.
The same thing occurs with 'around' in a less trivial way. 

Consider this : you want to track some log information on a method :
- when it started
- how long it lasted

So that you have a trace like :
Method XXX called for object YYY at HH:MM for XX seconds

Question : how will you track the start time so that you'll be able to
compute the duration and produce your log line ?
[Take into account that method XXX may be called recursively, or
concurently in a multi-threaded environment]

An 'around' advice will simplify your life as a user of aop (but not as
the designer of the aspect framework ;) this is were I ended with an
implementation that seemed quite complicate and that will need some rework
- and also why I didn't dare posting its UML description ;)

Bon courage,
Pedro



More information about the Python-list mailing list