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

Posted

技术标签:

【中文标题】调试:获取调用函数的文件名和行号?【英文标题】:Debugging: Get filename and line number from which a function is called? 【发布时间】:2014-08-17 19:19:45 【问题描述】:

我目前正在用 Python 构建一个相当复杂的系统,当我调试时,我经常将简单的打印语句放在几个脚本中。为了保持概览,我经常还想打印出 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 - what I actually want to print out here

为了更简单,我希望能够执行以下操作:

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() 函数的文件名和行号?

【问题讨论】:

使用logging 模块并配置格式化程序:docs.python.org/2/library/logging.html#logrecord-attributes。 Tornado Web 框架实际上有一个与此非常相似的格式,因此您可能可以使用他们的代码:tornado.readthedocs.org/en/latest/_modules/tornado/… 【参考方案1】:

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

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 ...

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

【讨论】:

这就是我已经做的,然后它打印出函数所在模块的文件名和行号,而不是我调用函数的位置。还有其他想法吗?【参考方案2】:

函数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)) # python3 syntax print

def grr(arg):
    debuginfo(arg)      # <-- stack()[1][0] for this line

grr("aargh")            # <-- stack()[2][0] for this line

输出

example.py:8 - aargh

【讨论】:

感谢您的帮助!我将在我正在从事的几乎每个项目中使用它!先生,您让我的生活变得更好,我现在可以正式称它为“棒极了”! 这太棒了! django 中有一个函数,我无法弄清楚它是从哪里调用的。 很酷的答案。这正是我正在寻找的。 stack() 的技巧很好......谢谢! 谢谢!我也使用了自省,但是使用工作补丁非常快。如果你包含 from __future__ import print_function, absolute_import 并且不使用 print 语句会更好。 我把它放在 a.py 中,然后在 b.py 中运行 grr()。控制台输出 a.py 作为路径。【参考方案3】:

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

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

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

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 堆栈中的哪个条目。

我展示了在该函数中提取了一些其他有趣的值,例如函数开始的行号、总堆栈深度和文件的完整路径。我没有展示它,但函数中的全局和局部变量也可以在检查中使用,以及对你下面的所有其他函数的完整堆栈跟踪。我在上面显示的信息足以进行分层调用/返回时间跟踪。实际上,从这里创建您自己的源代码级调试器的主要部分并没有那么远——而且大部分都只是坐在那里等待使用。

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

无论如何,我强烈建议您将检查代码导入您自己的一些 python 代码中,并查看它可以为您提供什么——特别是如果您可以在一个好的 python 调试器中单步执行您的代码。你会学到很多关于 python 是如何工作的,并看到这门语言的好处,以及幕后发生了什么使这成为可能。

带有时间戳的完整源代码级别跟踪是增强您对代码正在做什么的理解的好方法,尤其是在更动态的实时环境中。这种类型的跟踪代码的好处在于,一旦编写完成,您就不需要调试器支持来查看它。

【讨论】:

【参考方案4】:

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

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

【讨论】:

【参考方案5】:

printstack 包现在可以为您做到这一点:

import printstack

print('Hello!')

# Hello!          File "/link/to/your/file.py", line 3, in <module>

PyCharm 会自动使文件链接可点击/可关注。

通过pip install printstack安装。

【讨论】:

【参考方案6】:

使用字符串插值更新已接受的答案并显示调用者的函数名称。

import inspect
def debuginfo(message):
    caller = inspect.getframeinfo(inspect.stack()[1][0])
    print(f"caller.filename:caller.function:caller.lineno - message")

【讨论】:

以上是关于调试:获取调用函数的文件名和行号?的主要内容,如果未能解决你的问题,请参考以下文章

如何获取呼叫行号和文件名

20145234黄斐《信息安全系统设计基础》GDB调试汇编堆栈过程分析

linux c 用户态调试追踪函数调用堆栈以及定位段错误

js:当前函数获取调用它的函数

获取进行类调用的错误行号和文件名

python代码调试工具~pdb