views:

101

answers:

3

Hi,

I've been using the following code to trace the execution of my programs:

import sys
import linecache
import random

def traceit(frame, event, arg):
    if event == "line":
        lineno = frame.f_lineno
        filename = frame.f_globals["__file__"]
        if filename == "<stdin>":
            filename = "traceit.py"
        if (filename.endswith(".pyc") or
            filename.endswith(".pyo")):
            filename = filename[:-1]
        name = frame.f_globals["__name__"]
        line = linecache.getline(filename, lineno)
        print "%s:%s:%s: %s" % (name,  lineno,frame.f_code.co_name , line.rstrip())
    return traceit


def main():
    print "In main"
    for i in range(5):
        print i, random.randrange(0, 10)
    print "Done."

sys.settrace(traceit)
main()

Using this code, or something like it, is it possible to report the values of certain function arguments? In other words, the above code tells me "which" functions were called and I would like to know "what" the corresponding values of the input variables for those function calls.

Thanks in advance.

+1  A: 

web.py had a method called "upvars" that did something similar, taking in the variables from the calling frame. Note the comment:

def upvars(level=2):
    """Guido van Rossum sez: don't use this function."""
    return dictadd(
      sys._getframe(level).f_globals,
      sys._getframe(level).f_locals)
Frederik
+1  A: 

frame.f_locals will give you the values of the local variables, and I guess you could keep track of the last frame you've seen and if frame.f_back is not the lastframe dump frame.f_locals.

I'd predict though that you're pretty quickly going be snowed under with too much data doing this.

Here's your code modified to do this:

import sys
import linecache
import random

class Tracer(object):
    def __init__(self):
        self.lastframe = None

    def traceit(self, frame, event, arg):
        if event == "line":
            lineno = frame.f_lineno
            filename = frame.f_globals["__file__"]
            if filename == "<stdin>":
                filename = "traceit.py"
            if (filename.endswith(".pyc") or
                filename.endswith(".pyo")):
                filename = filename[:-1]
            name = frame.f_globals["__name__"]
            line = linecache.getline(filename, lineno)
            if frame.f_back is self.lastframe:
                print "%s:%s:%s: %s" % (name,  lineno,frame.f_code.co_name , line.rstrip())
            else:
                print "%s:%s:%s(%s)" % (name,  lineno,frame.f_code.co_name , str.join(', ', ("%s=%r" % item for item in frame.f_locals.iteritems())))

                print "%s:%s:%s: %s" % (name,  lineno,frame.f_code.co_name , line.rstrip())
                #print frame.f_locals
            self.lastframe = frame.f_back
        return self.traceit


def main():
    print "In main"
    for i in range(5):
        print i, random.randrange(0, 10)
    print "Done."

sys.settrace(Tracer().traceit)
main()
Duncan
Can you elaborate on your comment with an example. I'm not following you.
reckoner
No problem, I've added an example
Duncan
thanks. This helps a lot.
reckoner
+3  A: 

The traceit function that you posted can be used to print information as each line of code is executed. If all you need is the function name and arguments when certain functions are called, I would suggest using this trace decorator instead:

import functools

def trace(f):
    '''This decorator shows how the function was called'''
    @functools.wraps(f)
    def wrapper(*arg,**kw):        
        arg_str=','.join(['%r'%a for a in arg]+['%s=%s'%(key,kw[key]) for key in kw])
        print "%s(%s)" % (f.__name__, arg_str)
        return f(*arg, **kw)
    return wrapper

You could use it as follows:

@trace
def foo(*args,**kws):
    pass

foo(1)
# foo(1)       
foo(y=1)
# foo(y=1)
foo(1,2,3)
# foo(1,2,3)

Edit: Here is an example using trace and traceit in conjunction: Below, trace is used in 2 different ways. The normal way is to decorate functions you define:

@trace
def foo(i):
    ....

But you can also "monkey-patch" any function whether you defined it or not like this:

random.randrange=trace(random.randrange)

So here's the example:

import sys
import linecache
import random
import functools

def trace(f):
    '''This decorator shows how the function was called'''
    @functools.wraps(f)
    def wrapper(*arg,**kw):        
        arg_str=','.join(['%r'%a for a in arg]+['%s=%s'%(key,kw[key]) for key in kw])
        print "%s(%s)" % (f.__name__, arg_str)
        return f(*arg, **kw)
    return wrapper

def traceit(frame, event, arg):
    if event == "line":
        lineno = frame.f_lineno
        filename = frame.f_globals["__file__"]
        if filename == "<stdin>":
            filename = "traceit.py"
        if (filename.endswith(".pyc") or
            filename.endswith(".pyo")):
            filename = filename[:-1]
        name = frame.f_globals["__name__"]
        line = linecache.getline(filename, lineno)
        print "%s:%s:%s: %s" % (name,  lineno,frame.f_code.co_name , line.rstrip())
    return traceit

random.randrange=trace(random.randrange)

@trace
def foo(i):
    print i, random.randrange(0, 10)

def main():
    print "In main"
    for i in range(5):
        foo(i)
    print "Done."

sys.settrace(traceit)
main()
unutbu
This is neat, but it does not work with the trace code in the original post, so it does not improve the reporting of the traceit function shown.
reckoner
@reckoner: Your question asked, "Using this code, or something like it", so I figured you weren't committed to using that `traceit` function. Moreover, it is possible decorate functions with `@trace`, and use `traceit` at the same time.
unutbu
sorry for the confusion. I guess I am more committed to traceit than I thought. How can I use the decorator as you describe with traceit?
reckoner
Thanks! Big help.
reckoner