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 ;)

Friday, February 20, 2004

Evil returns

After months of having no good ideas for bad things to write, PEAK's NOT_GIVEN inspired me to write this little tidbit:

import gc, sys, struct, opcode, inspect

class NotGiven:

def notGiven(param):
caller = sys._getframe(1)
for potentialFunction in gc.get_referrers(caller.f_code):
if getattr(potentialFunction, 'func_code', None) == caller.f_code:
elif getattr(getattr(potentialFunction, 'im_func', None), 'func_code', None) == caller.f_code:
potentialFunction = caller.im_func
raise Exception("You're insane.")

argspec = inspect.getargspec(potentialFunction)
bytes = caller.f_code.co_code
lasti = caller.f_lasti
varStart = bytes.rindex(chr(opcode.opmap['LOAD_FAST']), 0, lasti)
(varIndex,) = struct.unpack('H', bytes[varStart+1:lasti])

value = argspec[3][varIndex]
return value is param

def foo(x = NotGiven(), y = NotGiven(), z = NotGiven()):
print 'x given?', not notGiven(x)
print 'y given?', not notGiven(y)
print 'z given?', not notGiven(z)

if __name__ == '__main__':
for args in ('', 'x', 'y', 'z', 'xy', 'xz', 'yz', 'xyz'):
print 'Passing', ' '.join(args) or 'nothing'

Thanks to teratorn for bringing NOT_GIVEN to my attention. :)

Saturday, February 7, 2004

Triple Headed X

Didn't work. For five hours. Arg.

I've run dual head for several months now, and it is cool. I recently picked up an old monitor for free, so I figured I'd go for a third head. Digging through old boxes, I found what must have been a 5 year old 3dfx card, cleaned off the dust as best I could, and stuck it in a PCI slot. NOT SUCCESS! I could find no configuration to make this combination of hardware work. Not that this surprised me greatly. It would have been nice if XFree86 were advanced enough to handle this, but seeing as the video cards were manufactured at least 5 years apart and by different companies, I won't hold this failure against X.

That was all a week ago. I ordered a PCI TNT2 after the 3dfx failure. Yesterday it arrived, amidst much excitement and fanfare, and was immediately installed. It worked just as well as the 3dfx, though. So I am unhappy, both with NVidia for not providing better documentation and XFree86 for not working. If anyone does know how to get a two-card, triple headed X configuration working, you are commanded to respond to this with details.