如何在引发异常时修改Python跟踪对象?

Jam*_*rke 21 python traceback

我正在研究第三方开发人员使用的Python库,为我们的核心应用程序编写扩展.

我想知道是否可以在引发异常时修改回溯,因此最后一个堆栈框架是在开发人员代码中调用库函数,而不是引发异常的库中的行.堆栈底部还有一些框架,其中包含对第一次加载我理想情况下要移除的代码时使用的函数的引用.

提前感谢任何建议!

use*_*826 12

您可以通过使用traceback的tb_next元素进行提升来轻松删除回溯的顶部:

except:
    ei = sys.exc_info()
    raise ei[0], ei[1], ei[2].tb_next
Run Code Online (Sandbox Code Playgroud)

tb_next是一个read_only属性,所以我不知道从底部删除东西的方法.您可能可以使用属性机制来允许访问属性,但我不知道如何执行此操作.

  • Python 3 中的等效项是: raise ei[0](ei[1]).with_traceback(ei[2].tb_next)` (4认同)
  • 知道如何在python 3中管理这个吗?我不能让自己工作. (3认同)

ber*_*ers 11

从Python 3.7开始,您可以实例化一个新traceback对象并.with_traceback()在抛出时使用该方法。sys._getframe(1)下面是一些使用任一(或更强大的替代方案)的演示代码,这些代码会引发一段AssertionError时间,使调试器相信错误发生在myassert(False)sys._getframe(1)省略顶部堆栈帧。

我应该补充的是,虽然这在调试器中看起来很好,但控制台行为揭示了它真正在做什么:

Traceback (most recent call last):
  File ".\test.py", line 35, in <module>
    myassert_false()
  File ".\test.py", line 31, in myassert_false
    myassert(False)
  File ".\test.py", line 26, in myassert
    raise AssertionError().with_traceback(back_tb)
  File ".\test.py", line 31, in myassert_false
    myassert(False)
AssertionError
Run Code Online (Sandbox Code Playgroud)

我没有删除堆栈顶部,而是添加了倒数第二帧的副本。

不管怎样,我关注的是调试器的行为方式,看起来这个调试器工作正常:

"""Modify traceback on exception.

See also https://github.com/python/cpython/commit/e46a8a
"""

import sys
import types


def myassert(condition):
    """Throw AssertionError with modified traceback if condition is False."""
    if condition:
        return

    # This function ... is not guaranteed to exist in all implementations of Python.
    # https://docs.python.org/3/library/sys.html#sys._getframe
    # back_frame = sys._getframe(1)
    try:
        raise AssertionError
    except AssertionError:
        traceback = sys.exc_info()[2]
        back_frame = traceback.tb_frame.f_back

    back_tb = types.TracebackType(tb_next=None,
                                  tb_frame=back_frame,
                                  tb_lasti=back_frame.f_lasti,
                                  tb_lineno=back_frame.f_lineno)
    raise AssertionError().with_traceback(back_tb)


def myassert_false():
    """Test myassert(). Debugger should point at the next line."""
    myassert(False)


if __name__ == "__main__":
    myassert_false()
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述


小智 8

看看jinja2在这里做了什么:

https://github.com/mitsuhiko/jinja2/blob/5b498453b5898257b2287f14ef6c363799f1405a/jinja2/debug.py

这很难看,但似乎做了你需要做的事情.我不会在这里复制粘贴示例,因为它很长.


Pet*_*sen 1

不改变回溯怎么样?您要求的两件事都可以通过不同的方式更轻松地完成。

  1. 如果开发人员的代码中捕获了库中的异常,并且引发了新的异常,那么原始的回溯当然会被丢弃。这就是通常处理异常的方式...如果您只允许引发原始异常,但您将其删除以删除所有“上部”框架,则实际的异常将没有意义,因为回溯中的最后一行不会本身能够引发异常。
  2. 要删除最后几帧,您可以请求缩短回溯...诸如traceback.print_exception()之类的东西采用“限制”参数,您可以使用它来跳过最后几个条目。

也就是说,如果你真的需要的话,应该很有可能修改回溯......但是你会在哪里做呢?如果在最顶层的某些包装器代码中,那么您可以简单地获取回溯,采取切片来删除您不需要的部分,然后使用“回溯”模块中的函数根据需要进行格式化/打印。

  • 我投了反对票,原因如下:这个答案提供了很好的建议,但它实际上并没有回答问题。 (26认同)
  • @BryanOakley:如果建议非常相关,并且如果它太大而无法放入评论中,那么它就属于答案。要么就是这样,要么让知识远离SO,这将是非常可悲的。所以我认为在这些情况下投反对票是不合适的。 (6认同)
  • @max:我想你是对的。我发现自己也在回答这样的问题——“你可以这样做,但为什么……还有其他一些事情需要考虑”。谢谢你让我负责。如果可以的话我会改变我的投票。 (3认同)
  • 必须不同意这样的论点“[它]没有意义,因为回溯中的最后一行本身无法引发异常”。取行“a += 1”。它看起来不“能够”引发异常(不包含“raise”一词)。但如果 `a` 是 `str` 类型,那么它就会发生 - 并且异常是有意义的。现在假设“a”是某个带有“__iadd__”的自定义类的实例,就像“str”一样,操作数是“int”是没有意义的。使用相同的语法,回溯现在将指向更深的位置,而不是错误所在的行。 (3认同)
  • 否决是因为我需要出于正当理由修改堆栈跟踪才能找到这一点。问题:“如何修改回溯”。回答:“你不”。相反,我会建议一种方法来做到这一点,同时强烈强调你不应该这么做。 (3认同)