Python调试:获取调用函数的文件名和行号?

问题描述 投票:22回答:4

我目前正在用Python构建一个非常复杂的系统,当我调试时,我经常将简单的print语句放在几个脚本中。为了保持概述,我经常还要打印出print语句所在的文件名和行号。我当然可以手动执行,或者使用以下内容:

from inspect import currentframe, getframeinfo
print getframeinfo(currentframe()).filename + ':' + str(getframeinfo(currentframe()).lineno) + ' - ', 'what I actually want to print out here'

它打印的东西像:

filenameX.py:273 - 我真的想在这里打印出来

为了使它更简单,我希望能够做到这样的事情:

print debuginfo(), 'what I actually want to print out here'

所以我把它放到某个地方的函数中并试着这样做:

from debugutil import debuginfo
print debuginfo(), 'what I actually want to print out here'
print debuginfo(), 'and something else here'

不幸的是,我得到:

debugutil.py:3 - what I actually want to print out here
debugutil.py:3 - and something else here

它打印出我定义函数的文件名和行号,而不是我调用debuginfo()的行。这很明显,因为代码位于debugutil.py文件中。

所以我的问题实际上是:如何获取调用此debuginfo()函数的文件名和行号?欢迎所有提示!

python debugging line-numbers inspect
4个回答
50
投票

函数inspect.stack()返回frame records列表,从调用者开始并移出,您可以使用它来获取所需的信息:

from inspect import getframeinfo, stack

def debuginfo(message):
    caller = getframeinfo(stack()[1][0])
    print "%s:%d - %s" % (caller.filename, caller.lineno, message)

def grr(arg):
    debuginfo(arg)

grr("aargh")

输出:

example.py:8 - aargh

3
投票

如果您将跟踪代码放在另一个函数中,并从主代码中调用它,那么您需要确保从祖父进程获取堆栈信息,而不是父代或跟踪函数本身

以下是3级深度系统的示例,以进一步阐明我的意思。我的main函数调用trace函数,该函数调用另一个函数来完成工作。

######################################

import sys, os, inspect, time
time_start = 0.0                    # initial start time

def trace_libary_init():
    global time_start

    time_start = time.time()      # when the program started

def trace_library_do(relative_frame, msg=""):
    global time_start

    time_now = time.time()

        # relative_frame is 0 for current function (this one), 
        # 1 for direct parent, or 2 for grand parent.. 

    total_stack         = inspect.stack()                   # total complete stack
    total_depth         = len(total_stack)                  # length of total stack
    frameinfo           = total_stack[relative_frame][0]    # info on rel frame
    relative_depth      = total_depth - relative_frame      # length of stack there

        # Information on function at the relative frame number

    func_name           = frameinfo.f_code.co_name
    filename            = os.path.basename(frameinfo.f_code.co_filename)
    line_number         = frameinfo.f_lineno                # of the call
    func_firstlineno    = frameinfo.f_code.co_firstlineno

    fileline            = "%s:%d" % (filename, line_number)
    time_diff           = time_now - time_start

    print("%13.6f %-20s %-24s %s" % (time_diff, fileline, func_name, msg))

################################

def trace_do(msg=""):
    trace_library_do(1, "trace within interface function")
    trace_library_do(2, msg)
    # any common tracing stuff you might want to do...

################################

def main(argc, argv):
    rc=0
    trace_libary_init()
    for i in range(3):
        trace_do("this is at step %i" %i)
        time.sleep((i+1) * 0.1)         # in 1/10's of a second
    return rc

rc=main(sys.argv.__len__(), sys.argv)
sys.exit(rc)

这将打印如下:

$ python test.py 
    0.000005 test.py:39           trace_do         trace within interface func
    0.001231 test.py:49           main             this is at step 0
    0.101541 test.py:39           trace_do         trace within interface func
    0.101900 test.py:49           main             this is at step 1
    0.302469 test.py:39           trace_do         trace within interface func
    0.302828 test.py:49           main             this is at step 2

顶部的trace_library_do()函数是您可以放入库中,然后从其他跟踪函数调用它的示例。相对深度值控制python堆栈中的哪个条目被打印。

我展示了在该函数中拉出一些其他有趣的值,比如函数的起始行号,总堆栈深度以及文件的完整路径。我没有显示它,但函数中的全局变量和局部变量也可以在inspect中使用,以及完整的堆栈跟踪到你下面的所有其他函数。有足够的信息与我上面显示的内容进行分层调用/返回时序跟踪。从这里创建自己的源代码级调试器的主要部分实际上并没有那么多 - 而且它们大多只是坐在那里等待使用。

我确信有人会反对我正在使用内部字段和检查结构返回的数据,因为很可能有访问函数为你做同样的事情。但是我通过在python调试器中单步执行这类代码来找到它们,并且它们至少在这里起作用。我正在运行python 2.7.12,如果你运行的是另一个版本,你的结果可能非常好。

在任何情况下,我强烈建议您将inspect代码导入到您自己的某些python代码中,并查看它可以为您提供的内容 - 特别是如果您可以在一个优秀的python调试器中单步执行代码。您将学习很多关于python如何工作的知识,并且可以看到语言的好处,以及为实现这一目标而幕后发生的事情。

使用时间戳进行完整的源级别跟踪是增强您对代码执行情况的理解的好方法,尤其是在更多动态实时环境中。关于这种类型的跟踪代码的好处是,一旦编写完成,您就不需要调试器支持来查看它。


0
投票

只需将您发布的代码放入函数中:

from inspect import currentframe, getframeinfo

def my_custom_debuginfo(message):
    print getframeinfo(currentframe()).filename + ':' + str(getframeinfo(currentframe()).lineno) + ' - ', message

然后根据需要使用它:

# ... some code here ...
my_custom_debuginfo('what I actually want to print out here')
# ... more code ...

我建议您将该功能放在一个单独的模块中,这样您就可以在每次需要时重复使用它。


0
投票

发现这个问题是为了一个有点相关的问题,但我想要更多细节:执行(我不想安装整个调用图包)。

如果您需要更详细的信息,可以使用标准库模块traceback检索完整的回溯,并使用traceback.extract_stack()存储堆栈对象(元组列表)或使用traceback.print_stack()将其打印出来。这更适合我的需求,希望它能帮助别人!

© www.soinside.com 2019 - 2024. All rights reserved.