Archive

Posts Tagged ‘AOP’

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