Wednesday, December 07, 2005

I've been programming some mind-bending algorithms in Python lately, and was truly irritated to be unable to find a Python analogue for Common Lisp's TRACE macro. Specifically, TRACE like clisp's TRACE. So, I built my own. This TRACE notifies you function invocations and returns, which makes it a lot more useful than the line-based TRACE offered by Python itself. My trace implementation only works on methods, not functions, so it's a little limited. When you call funtrace on a method name, it replaces the method with a wrapper that prints the name of the method, its arguments, and then calls the method itself. When the method returns, it prints which method finished and the return values. Sometimes you don't want to print all the arguments (because one is a gigantic dict and fills the entire screen when printed), which is what the second argument of funtrace is for. If you set this argument to False, all arguments are suppressed. If you set it to a numerical value n, only the first n arguments to the function will be printed (useful if you pass the dict in last). Each time a traced function is called, a global indent variable is incremented, and each time a traced function exits, the indent variable is decremented. If you print debugging statements using trprint, your output will be indented appropriately as well. If you want things that look like function invocations, for example at the beginning and end of a block, you can use trenter and trleave, just remember to balance them or you'll get very strange results. Both of these simply print their argument and do the indent bookkeeping. trace.py
indent = 0

from sys import stdout
import sys

def trprint(*args):
    global indent
    sys.stdout.write(indent * "   ")
    sys.stdout.write(*args)
    sys.stdout.write("\n")

def trenter(name):
    global indent
    trprint("ENTERING %s:" % name)
    indent += 1

def trleave(name):
    global indent
    indent -= 1
    trprint("LEAVING %s." % name)

def trace(func, printArgs):
    def inner(*args):
        global indent
        if printArgs:
            trenter("%s%s" % (func.__name__, args[:printArgs]))
        else:
            trenter("%s(...)" % (func.__name__))
        rt = func(*args)
        if printArgs:
            trleave("%s%s = %s" % (func.__name__, args[:printArgs], rt))
        else:
            trleave("%s(...) = %s" % (func.__name__, rt))
        return rt
    return inner

def funtrace(func, printArgs):
    func.im_class.__dict__[func.__name__] = trace(func, printArgs)
    return func
example.py
from trace import funtrace
from trace import trprint
class foo:
   def __str__(self): return ""
   def __repr__(self): return str(self)
   def bar(self, x):
     trprint("X = %s" % x)
     if x != 0:
        self.bar(x - 1)
     return x

funtrace(foo.bar, 3)
foo().bar(1)
This produces
ENTERING bar(, 3)
   X = 3
   ENTERING bar(, 2)
     X = 2
     ENTERING bar(, 1)
       X = 1
       ENTERING bar(, 0)
         X = 0
       LEAVING bar(, 0) = 0
     LEAVING bar(, 1) = 1
   LEAVING bar(, 2) = 2
LEAVING bar(, 3) = 3
Quite useful!

0 Comments:

Post a Comment

<< Home