Posted by virantha on Fri 02 November 2012

Class-based decorators in Python

I recently started using decorators in python (2.7) to clean up some existing code, and one big hurdle I had to surmount was the dearth of accurate information on using class-based decorators. The few examples I found were quite buggy, and it seemed that most people did not use decorator classes, which is a shame because in addition to being very flexible, they expose the underlying mechanics of method/function decoration better than functions.

Here's an example of one such use: in EDA tool flows, we often do many text file transformations (in fact, it's been argued that all EDA work basically boils down to transforming information from one file format to another :-) ). Sometimes, a lot of intermediate files get generated on the way to the final output, such as when taking a structural circuit description and outputting a spice netlist that has the proper name mangling for the end simulator or netlist comparison engine. In these instances, it would be far preferable to do all the work in a tmp directory to reduce file clutter in our source directories, so a lot of functions end up with code that looks like the following:

def someMunge (self, filename):
  currentDir = os.getcwd()
  .... actual work ....

In fact, I've omitted a lot of the error-checking and cleanup code that we would end up using. Once we start having a lot of different munge functions, all this boilerplate code needs to be factored out to keep it maintainable, so we might end up doing the following:

def someMunge (self, filename):
    .... actual work ....

Seems reasonable, although it's still cluttering up the munge function with extraneous detail unrelated to the actual munge. A much cleaner solution is being able to do the following:

def someMunge (self, filename):
.... actual work ....

The @tmpdir applies the tmpdir decorator to this class method. And here is what the tmpdir decorator class looks like:

class tmpdir(object):
    def __init__(self, f):
    # f is the method being decorated, so save it so we can call it
    self.f = f

def __get__(self, instance, owner):
    # Save a ptr to the object being decorated
    self.cls = owner
    self.obj = instance
    return self.__call__

def __call__(self, *args, **kwargs):
    # The actual meat of the decorator
    currentDir = os.getcwd()

    # Call the original method being decorated
    r = self.f.__call__(self.obj, *args, **kwargs)

    return r

A more detailed explanation of how it works is coming up soon!

© Virantha Ekanayake. Built using Pelican. Modified svbhack theme, based on theme by Carey Metcalfe