Home > Programming, Python > Aspect-oriented Python and Metaclasses

Aspect-oriented Python and Metaclasses

February 25th, 2009

I came across a nice introductory post on AOP in Python authored by Dethe Elza titled Aspect-oriented Python.

He refers to two main tactics when dealing with before/after aspects in Python 2.5 and above: Decorators using the @ syntax and context managers using the “with” keyword.

I’d like to add one more tactic to his list. The Metaclass approach.

Metaclasses are a good fit for Aspect-oriented programming in Python.

I’ll demonstrate this through a simple example.

import inspect
import logging
import re

def before(fn):
    def wrapped(*args, **kws):
        logging.warn('about to call function %s' % fn.func_name)
        return fn(*args, **kws)
    return wrapped

def after(fn):
    def wrapped(*args, **kws):
        retVal = fn(*args, **kws)
        logging.warn('just returned from function %s' % fn.func_name)
        return retVal
    return wrapped

class SimpleLoggingMeta(type):
    def __init__(cls, name, bases, ns):
        # get list of decorators
        decorators = ns.get('decorators', (before, after,))
        # get method_pattern
        method_pattern = ns.get('method_pattern', '.*')
        for key, value in ns.items():
            # skip the constructor and objects
            # in the namespace that aren't methods
            if key in ('__init__') or \
                not inspect.isfunction(value): continue

            # check if method matches the method pattern
            if re.search(method_pattern, key):
                # apply the decorators
                for decorator in decorators: value = decorator(value)
                setattr(cls, key, value)

class Person(object):
    __metaclass__ = SimpleLoggingMeta

    def __init__(self, first, middle, last):
        self.first = first
        self.middle = middle
        self.last = last

    def name(self):
        logging.warn('inside name')
        return '%s %s' % (self.first, self.last)

    def full_name(self):
        logging.warn('inside full_name')
        return '%s %s %s' % (self.first, self.middle, self.last)

    def initials(self):
        logging.warn('inside initials')
        return '%s%s%s' % \
            (self.first[0], self.middle[0], self.last[0])

if __name__ == "__main__":
    person = Person('Joe', 'Plumber', 'Sixpack')
    person.name()
    person.full_name()
    person.initials()

Output:

WARNING:root:about to call function name
WARNING:root:inside name
WARNING:root:just returned from function wrapped
WARNING:root:about to call function full_name
WARNING:root:inside full_name
WARNING:root:just returned from function wrapped
WARNING:root:about to call function initials
WARNING:root:inside initials
WARNING:root:just returned from function wrapped

In the example above, the SimpleLoggingMeta metaclass simplifies the decoration process by automatically applying decorators that are declared in the metaclass or the namespace of the class itself.

Classes can define their own “decorators” and “method_pattern” attributes if metaclass defaults don’t satisfy the concerns.

We will now go ahead and adjust the Person class to apply the before, after and property decorators to all methods of the class that have the following regex pattern “.*name$

class Person(object):
    __metaclass__ = SimpleLoggingMeta
    decorators = (before, after, property,)
    method_pattern = ".*name$"

    def __init__(self, first, middle, last):
        self.first = first
        self.middle = middle
        self.last = last

    def name(self):
        logging.warn('inside name')
        return '%s %s' % (self.first, self.last)

    def full_name(self):
        logging.warn('inside full_name')
        return '%s %s %s' % (self.first, self.middle, self.last)

    def initials(self):
        logging.warn('inside initials')
        return '%s%s%s' % \
            (self.first[0], self.middle[0], self.last[0])

if __name__ == "__main__":
    person = Person('Joe', 'Plumber', 'Sixpack')
    person.name
    person.full_name
    person.initials()

Output:

WARNING:root:about to call function name
WARNING:root:inside name
WARNING:root:just returned from function wrapped
WARNING:root:about to call function full_name
WARNING:root:inside full_name
WARNING:root:just returned from function wrapped
WARNING:root:inside initials

So what happened? The before, after and property decorators were applied to the methods that match the method pattern “.*name$” which in the case of the Person class is the name and the full_name methods.

The above example is by no means perfect, but I hope you get the idea.

Metaclasses don’t have to be scary. :-)

admin Programming, Python , ,

  1. February 25th, 2009 at 19:38 | #1

    Hi there. I’m glad you liked the AOP article. Thanks for pointing out that Metaclasses can be used to apply the decorators. I’m pretty sure class decorators can also do this, and feel a bit cleaner to me than metaclasses (personal preference). One distinction between using metaclasses and class decorators (besides the fact that class decorators are not available before Python 2.6) is that metaclasses are inherited by subclasses and class decorators are not. Both are useful.

  2. February 26th, 2009 at 00:06 | #2

    @Dethe Elza

    Thanks for your reply. I agree that both class decorator and metaclass can be useful having their own pros and cons. One thing that sticks out for me is how simple AOP concepts really can be in Python, so simple that most of the time its natural to the language itself hence we don’t see much buzz around AOP and python. Giving AOP a try in statically typed language, such as Java, it’s mind blowing maze of complexity.

  3. DrKJam
    July 7th, 2010 at 23:29 | #3

    The AOP link is broken, here is the updated URL :-

    http://livingcode.org/entries/2009-02-24_aspect-oriented-python/

  1. No trackbacks yet.