开发者

How to use traceit to report function input variables in stack trace

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.

Tha开发者_开发知识库nks in advance.


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


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


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)


What is much more useful to me in tracing than dumping ALL the state of variables at the time of execution is to do an eval of each and every line of code, ie:

for modname in modnames:                    | for init in ., init, encoding
                                            |
    if not modname or '.' in modname:       | if not init or '.' in init
         continue                           | continue
                                            |
    try:                                    |

ie: where real line of code is on the left and each line of code is on the right. I've implemented this in perl and it is a LIFESAVER there. I'm in the process of trying to implement this in python but I'm not as familiar with the language so it will take a bit of time.

Anyways, if anybody has ideas how to implement this, I'd love to hear them. As far as I can tell, it comes down to this function

 interpolate_frame(frame, string)

where is the frame passed to the trace function, and the string is the line of code to be interpolated with variables in the current frame. Then, the above code becomes:

 print "%s:%s:%s: %s|%s" % (name,  lineno,frame.f_code.co_name, 
     padded(line.rstrip(),10),padded(interpolate_frame(frame, line.rstrip()),100)

I'm going to try to hack this up, but again, if anybody has ideas on this I'm welcome to hear them.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜