6

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

我可以从回溯对象中获取调用行号,但是如果该行上有多个函数调用(可能是同一个函数)(例如,作为更大表达式中的子表达式),那么我如何知道这个调用来自哪里?即使从源代码行开始的偏移量我也会很高兴。

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

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

4

4 回答 4

6

Traceback 帧也有一个行号:

lineno = traceback.tb_lineno

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

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

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

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

在任何情况下,这都是traceback模块用来将回溯转换为有用信息的方法。

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

您可以使用astmodule执行此操作,但您不能逐行执行此操作,因为您需要范围上下文来为本地、单元格和全局名称查找生成正确的字节码,例如。

于 2012-12-19T12:09:50.817 回答
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        

由于直接编译代码应该与编译 via 相同ast.parse,并且由于弄乱行号不应影响生成的字节码(除了co_lnotab),您应该能够:

  • 找到源文件
  • 解析它ast.parse
  • munge ast 中的行号以包括列偏移量
  • 编译 ast
  • 使用tb_lasti搜索 mungedco_lnotab
  • 将 munged 行号转换回(行号,列偏移)
于 2012-12-19T14:17:27.287 回答
1

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

答案很长,所以这里是链接

于 2015-02-01T08:22:56.440 回答
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

于 2013-11-27T14:25:15.117 回答