Sunday, February 29, 2004

Generating Python functions from a template

One thing I often use metaclasses for is removing boilerplate from class definitions. For example, if I have a group of functions which I want to all handle a particular error condition in the same way, I might write a metaclass like this:

def logAndContinue(f):
def doit(*a, **kw):
try: return f(*a, **kw)
except: log.err()
return doit

class ErrorLoggingType(type):
def __new__(cls, name, bases, attrs):
for (fname, func) in attrs.items():
attrs[fname] = logAndContinue(func)
return type.__new__(cls, name, bases, attrs)

class Errorful(object):
__metaclass__ = ErrorLoggingType

def error(self): 1/0

This is almost always up to the task, but last night I discovered a case where it didn't do all I wanted. The problem arose from the fact that, in a traceback, Python uses the code object's name, not the function object's name. In the above example, this would result in "doit" showing up in the traceback, instead of "error". So, I hacked up the following to get a code object with the name I wanted:

def formatArgs((args, varargs, kwargs, defaults)):
if defaults is None:
argString = ', '.join(args)
namespace = {}
argString = ', '.join(args[:-len(defaults)])
defaultArgs = zip(args[-len(defaults):], defaults)
dfltString = ['%s=%s' % (a, a) for (a, v) in defaultArgs]
defaultArgString = ', '.join(dfltString)
if argString:
argString += ', ' + defaultArgString
argString = defaultArgString
namespace = dict(defaultargs)
if varargs:
argString += ', *' + varargs
if kwargs:
argString += ', **' + kwargs
return argString, namespace

def _abcMethod(name, func):
s = "def %s(%s): raise NotImplementedError\n"
argspec, namespace = formatArgs(inspect.getargspec(func))
s = s % (name, argspec)
exec s in namespace
return namespace.pop(name)

As is hinted by the function "_abcMethod", I used this to generate functions for a generated Abstract Base Class version of a defined Interface. Since the only thing these functions do is raise an exception, it was somewhat more important than usual to have their names be correct.

formatArgs could be replaced by a call to new.code, but because new.code takes 14 arguments, I don't think the code would be any simpler ;)


  1. Curious what advantages/disadvantages this has WRT to decorators.

  2. I think they're pretty much orthogonal. Care to elaborate?