如何使用traceit报告栈 trace 中的函数输入变量

1 投票
4 回答
3067 浏览
提问于 2025-04-15 21:29

我一直在用以下代码来跟踪我的程序执行情况:

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

使用这段代码,或者类似的代码,能不能报告某些函数参数的值?换句话说,上面的代码告诉我“哪些”函数被调用了,但我想知道这些函数调用时输入变量的“具体值”。

提前谢谢你们。

4 个回答

1

web.py 有一个叫做 "upvars" 的方法,它的功能类似,可以获取调用这个方法时的变量。请注意下面的注释:

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

你发的traceit函数可以用来在每一行代码执行时打印信息。如果你只需要在某些函数被调用时获取函数名和参数,我建议你使用这个trace装饰器:

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

你可以这样使用它:

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

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

补充:这里有一个例子,展示了如何同时使用tracetraceit: 下面,trace有两种不同的用法。正常的用法是装饰你定义的函数:

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

但你也可以像这样“猴子补丁”任何函数,无论你是否定义了它:

random.randrange=trace(random.randrange)

所以这是一个例子:

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

frame.f_locals 可以让你看到当前函数里的局部变量的值。我想你可以记录下你最后看到的那个帧,如果 frame.f_back 不是你记录的最后一个帧,就把 frame.f_locals 的内容打印出来。

不过我觉得这样做很快就会让你收到太多的数据,可能会有点混乱。

下面是修改过的代码,可以实现这个功能:

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

撰写回答