Archive

Posts Tagged ‘Python’

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

Aggregating data collections made simple in Django 1.1

February 24th, 2009

Aggregation, the new addition to Django QuerySet in version 1.1, makes it really simple to summarizing data collections.

I am currently working on a site that will provide free online petition hosting with a key focus on the South African public.
I decided to have an embarrassingly simple “Top 10″ page on the site that will show currently popular petitions. The new Django aggregation support makes this task simple.

Requirement:
List top 10 petitions grouping them by “total number of signatures” and “newest signature, published date” per petition.
Order petitions in descending order by “total number of signatures” and “newest signature, published date”.

Simple solution using the new Django annotate() clause:

top_petitions = Petition.objects.annotate(
    Count("petitionsignature"),
    Max("petitionsignature__pub_date")
).order_by("-petitionsignature__count",
    "-petitionsignature__pub_date__max")[:10]

>>> top_petitions[0].petitionsignature__count
5
>>> top_petitions[0].petitionsignature__pub_date__max
datetime.datetime(2009, 2, 15, 18, 55, 23, 263325)

Both the above attributes, petitionsignature__count and petitionsignature__pub_date__max are name identifiers for the aggregate values. By default, the name is automatically generated from the name of the field and the aggregate function name separating the two with a double underscore. You can manually specify the name for the aggregate value by defining it directly in the aggregate or annotate clause.

For more information on Django aggregation support, you can read the official guide here: http://docs.djangoproject.com/en/dev/topics/db/aggregation/

alen Programming, Python , ,