如何在跟踪Python函数时获取调用表达式?

时间:2012-12-19 11:59:48

标签: python debugging tracing

当内部跟踪功能,调试函数调用时,是否可以以某种方式检索调用表达式?

我可以从traceback对象中调用行号,但是如果该行上有多个函数调用(可能是相同的函数)(例如,作为更大表达式中的子表达式),那么我怎样才能知道这个调用的来源?即使从源线的起点偏移,我也会很高兴。

traceback.tb_lasti似乎提供了更多的粒度上下文(尝试了最后一个字节码的索引) - 是否有可能将字节码连接到其确切的源范围?

编辑:只是为了澄清 - 我需要从调用源代码行中提取特定的(子)表达式(callsite)。

4 个答案:

答案 0 :(得分:5)

回溯帧也有一个行号:

lineno = traceback.tb_lineno

您还可以访问代码对象,该代码对象将具有名称和文件名:

name = traceback.tb_frame.f_code.co_name
filename = traceback.tb_frame.f_code.co_filename

您可以使用文件名和行号,以及帧全局和linecache module来有效地将其转换为正确的源代码行:

linecache.checkcache(filename)
line = linecache.getline(filename, lineno, traceback.tb_frame.f_globals)

这就是traceback module在任何情况下用于将回溯转换为有用信息的内容。

由于字节码只有一个与之关联的行号,因此你不能直接将字节码引回到源代码行的精确部分;你必须自己解析那一行,以确定每个部分将发出什么字节码,然后将其与代码对象的字节码相匹配。

您可以使用ast module执行此操作,但是您无法逐行执行此操作,因为您需要使用范围上下文来生成本地与单元格与全局名称外观的正确字节码例如,特写镜头。

答案 1 :(得分:3)

不幸的是,编译后的字节码丢失了列偏移量;行号映射的字节码索引包含在co_lnotab行号表中。 dis模块是查看字节码和解释co_lnotab的好方法:

>>> dis.dis(compile('a, b, c', '', 'eval'))
  1           0 LOAD_NAME                0 (a)
              3 LOAD_NAME                1 (b)
              6 LOAD_NAME                2 (c)
              9 BUILD_TUPLE              3
             12 RETURN_VALUE        
  ^-- line number

然而,没有什么可以阻止我们弄乱行号:

>>> a = ast.parse('a, b, c', mode='eval')
>>> for n in ast.walk(a):
...     if hasattr(n, 'col_offset'):
...         n.lineno = n.lineno * 1000 + n.col_offset
>>> dis.dis(compile(a, '', 'eval'))
1000           0 LOAD_NAME                0 (a)

1003           3 LOAD_NAME                1 (b)

1006           6 LOAD_NAME                2 (c)
              9 BUILD_TUPLE              3
             12 RETURN_VALUE        

由于直接编译代码应该与通过ast.parse进行编译相同,并且因为乱码行不应该影响生成的字节码(除了co_lnotab),你应该能够:

  • 找到源文件
  • 使用ast.parse
  • 解析它
  • 使用ast中的行号来包含列偏移
  • 编译ast
  • 使用tb_lasti搜索被提供的co_lnotab
  • 将munged行号转换回(行号,列偏移)

答案 2 :(得分:1)

我知道这是一种死灵法术,但我昨天发布了一个类似的问题而没有先看到这个问题。因此,万一有人感兴趣,我使用Python3中的inspectast模块以不同于接受的答案的方式解决了我的问题。它仍然用于调试和教育目的,但它可以解决问题。

答案相当长here is the link

答案 3 :(得分:0)

这就是我最终解决问题的方法:我通过将其包含在对辅助函数的调用以及有关原始调用的源位置的信息中来检测原始程序中的每个函数调用。实际上我有兴趣控制程序中每个子表达式的评估,所以我包装了每个子表达式。

更确切地说:当我在原始程序中有一个表达式e时,它就变成了

_after(_before(location_info), e)

在仪表程序中。助手的定义如下:

def _before(location_info):
    return location_info

def _after(location_info, value):
    return value

当跟踪器报告了对_before的调用时,我知道它将评估由location_info表示的位置处的表达式(跟踪系统使我能够访问局部变量/参数,这就是我的方法知道location_info)的价值。当跟踪器报告了对_after的呼叫时,我知道location_info指示的探测刚刚被评估,值在value

我本可以将执行“事件处理”直接写入这些辅助函数并完全绕过跟踪系统,但我也是出于其他原因需要它,所以我只使用这些帮助程序来触发跟踪中的“调用”事件系统

结果可以在这里看到:http://thonny.org