watch this The wheels are turning, slowly turning. home
Generating Python functions from a template 2004-02-29

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 = {}
    else:
        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
        else:
            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 ;)