【问题标题】:How to get calling expression when tracing a Python function?跟踪 Python 函数时如何获取调用表达式?
【发布时间】:2012-12-19 11:59:47
【问题描述】:

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

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

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

编辑:澄清一下——我需要从调用源代码行中提取特定的(子)表达式(调用点)。

【问题讨论】:

  • @Alexey:很遗憾没有,源代码对我来说不够精确,我需要提取特定的子表达式。
  • 也许如果您粘贴源代码行、相关字节码和您正在调试的指令,我们可以帮助您确定该指令在源代码中的位置。
  • @TokenMacGuy:我没有调试任何特定代码,我想在 Python 跟踪工具之上创建一个可视化表达式调试器。

标签: python debugging trace


【解决方案1】:

回溯帧也有行号:

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 执行此操作,但您不能逐行执行此操作,因为您需要范围上下文来为本地、单元格和全局名称查找生成正确的字节码,例如。

【讨论】:

  • 问题是行号(或源行本身)对我来说不够精细,我需要从该行中提取特定的调用点。
  • @Aivar:那么您必须解析出字节码(来自.f_code 对象加上偏移量)。这涉及更多。
  • @Martin:我不怀疑它参与其中,但你知道,字节码是否仅包含行级别或字符级别的源位置?从 Python 文档中我得到的印象是字节码指令只有与它们相关的源代码行号。
  • @Aivar:它只有行号。
【解决方案2】:

不幸的是,编译后的字节码丢失了它的列偏移;字节码索引到行号映射包含在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编译是一样的,并且由于弄乱行号不应该影响生成的字节码(@除外) 987654327@),您应该能够:

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

【讨论】:

  • 谢谢!这看起来很有希望,我会试一试。
【解决方案3】:

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

答案比较长,所以here is the link

【讨论】:

    【解决方案4】:

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

    更准确地说:当我在原始程序中有一个表达式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)。当 tracer 报告调用 _after 时,我知道 location_info 指示的表达式刚刚被评估,值在 value 中。

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

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

    【讨论】:

      猜你喜欢
      • 2012-08-04
      • 1970-01-01
      • 2015-03-30
      • 1970-01-01
      • 1970-01-01
      • 2014-05-23
      • 2010-09-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多