aspect-oriented demo using metaclasses

Mark McEahern marklists at mceahern.com
Sun Jun 30 19:46:49 CEST 2002


Pedro, thanks for the reply.  Comments follow inline below.  Some of my
comments will refer to the latest version of code that I just posted
separately.

[Pedro]
> If you really want something more consistent than my posting, I will
> recommend you to also check:
[snip]
>     - Pythius by Juergen Hermann
>     - Transwarp by Phillip J. Eby
[snip]

I've looked at Transwarp and failed to get it.  Last time I looked there was
no simple demo that showed what problem it was trying to solve and how TW
solves it.  But I'm dense and I should probably give it another try.  I look
forward to checking out Pythius.  Someone else mentioned that
recently--probably in the recent thread on metaclasses.

I wrote:
> (I'm not worried about distinguishing staticmethod and classmethod
> type methods for now):

[Pedro]
> 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 ;)

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.  I just did a
little interactive session to verify that I can do this too:

  type(staticmethod(lambda x:x))
  type(classmethod(lambda x:x))

I can see the outlines of some sort of method wrapper factory even now:

  anon = lambda x:x
  # f, g, and h are make_wrapped_method variants for each type of method.
  wrapper_factory = {type(anon): f,
                     type(staticmethod(anon)): g,
                     type(classmethod(anon)): h}
  for k, v in dict.items():
    wrapper = wrapper_factory[type(v)]
    ...

> 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

Great catch!  Thanks.  I fixed that in the latest version.

> This Trace class is an aspect.

Ah, I see that I was calling the metaclass Aspect whereas Trace is really an
Aspect.  I definitely need help with naming and terminology, as you can
tell.  I renamed the metaclass to Aspected.  Does that seem better or am I
still missing the correct term?

> This is not an Aspect. This is your way to implement method call
> interception.

Thank you.

> What about module functions ? Bounded methods ?
> Using metaclass for call interception is too restrictive I believe.

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.  ;-)

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 should say that my motivation for pursuing a metaclass implementation is
partly just to learn metaclasses, 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.

> 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.

Yup, I fixed this.

> - providing a function, and take benifit of nested_scopes to retrieve
>   all the information from your context

That's the approach I took.  And to think, 6 months ago I had no freaking
idea what the point of nested scopes was.  ;-)

> 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.

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.

> Yes. Interception of raised exception is a good (and easy ;) feature.
> Just try to go a step further with 'around' methods.

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?

> 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 ;)

You're very welcome.  Your comments have been most helpful.

> 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 :

[snip]

Wow, that's eye opening.  I look forward to seeing more.  In the meantime,
I'm going to reflect on whether to abandon the metaclass approach.  It does
seem, as you say, restrictive.  That is, it doesn't allow sufficient runtime
dynamism and it seems to require modifying the code-to-be-aspected.

Cheers,

// mark

-






More information about the Python-list mailing list