从异常对象中提取回溯信息

给定一个异常对象(来源不明)有办法获得它的回溯吗? 我有这样的代码:

def stuff():
try:
.....
return useful
except Exception as e:
return e


result = stuff()
if isinstance(result, Exception):
result.traceback <-- How?

一旦我拥有了 Exception 对象,如何从中提取回溯?

88536 次浏览

这个问题的答案取决于您使用的 Python 版本。

在 Python 3中

这很简单: 异常都带有包含回溯的 __traceback__属性。这个属性也是可写的,可以使用异常的 with_traceback方法方便地设置:

raise Exception("foo occurred").with_traceback(tracebackobj)

作为 raise文档的一部分,对这些特性进行了最低限度的描述。

这部分答案的所有功劳都应该归功于 Vyctor。我把它包含在这里只是因为这个答案停留在顶部,而 Python 3正变得越来越普遍。

在 Python 2中

太复杂了。回溯的问题在于它们引用了堆栈帧,而堆栈帧引用了引用了堆栈帧的回溯。这会给垃圾收集器带来问题。(感谢 Ecatmur首先指出这一点。)

解决这个问题的好办法是在离开 except子句之后手术式地使用 打破这个循环,这正是 Python 3所做的。Python2解决方案要丑陋得多: 提供了一个 ad-hoc 函数 sys.exc_info(),它是 只能在except条款。它返回一个元组,其中包含当前正在处理的异常、异常类型和异常的回溯。

因此,如果您在 except子句中,那么可以使用 sys.exc_info()的输出和 traceback模块来完成各种有用的事情:

>>> import sys, traceback
>>> def raise_exception():
...     try:
...         raise Exception
...     except Exception:
...         ex_type, ex, tb = sys.exc_info()
...         traceback.print_tb(tb)
...     finally:
...         del tb
...
>>> raise_exception()
File "<stdin>", line 3, in raise_exception

但是正如您的编辑所指出的那样,如果在处理了 已经之后仍然没有处理异常,那么您将尝试获得 已经打印的回溯。这个问题难得多。不幸的是,当没有处理异常时,sys.exc_info返回 (None, None, None)。其他相关的 sys属性也没有帮助。当没有异常被处理时,sys.exc_traceback是不推荐和未定义的; sys.last_traceback看起来很完美,但它似乎只在交互式会话期间被定义。

如果可以控制如何引发异常,则可以使用 inspect自定义异常来存储一些信息。但我不太确定这是怎么回事。

说实话,捕获并返回异常是一种不寻常的做法。这可能是您无论如何都需要重构的信号。

回溯没有存储在异常中有一个很好的理由; 因为回溯保存了对其堆栈局部变量的引用,这将导致循环引用和(临时)内存泄漏,直到循环 GC 发挥作用。(这就是为什么你应该 永远不要将回溯存储在局部变量中。)

我能想到的唯一一件事就是你给 stuff的全局变量打补丁,这样当它认为它在捕捉 Exception的时候,它实际上是在捕捉一个特定的类型,而异常作为调用者传播给你:

module_containing_stuff.Exception = type("BogusException", (Exception,), {})
try:
stuff()
except Exception:
import sys
print sys.exc_info()

因为 Python 3.0 < sup > [ PEP 3109] ,内置的类 Exception有一个 __traceback__属性,其中包含一个 traceback object(使用 Python 3.2.3) :

>>> try:
...     raise Exception()
... except Exception as e:
...     tb = e.__traceback__
...
>>> tb
<traceback object at 0x00000000022A9208>

问题是,在使用 谷歌 __traceback__一段时间后,我发现只有很少的文章,但没有一篇描述是否或为什么应该(不)使用 __traceback__

然而,raise的 Python 3文档表示:

当引发异常并作为 __traceback__属性附加到异常时,通常会自动创建回溯对象,这是可写的。

所以我猜它是用来。

一种从 Python 3中的异常对象获得字符串回溯的方法:

import traceback


# `e` is an exception object that you get from somewhere
traceback_str = ''.join(traceback.format_tb(e.__traceback__))

traceback.format_tb(...) 返回一个字符串列表,''.join(...)将它们连接在一起。

顺便说一句,如果你真的想得到完整的回溯,你会看到它打印到你的终端,你想这样:

>>> try:
...     print(1/0)
... except Exception as e:
...     exc = e
...
>>> exc
ZeroDivisionError('division by zero')
>>> tb_str = traceback.format_exception(etype=type(exc), value=exc, tb=exc.__traceback__)
>>> tb_str
['Traceback (most recent call last):\n', '  File "<stdin>", line 2, in <module>\n', 'ZeroDivisionError: division by zero\n']
>>> print("".join(tb_str))
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ZeroDivisionError: division by zero

如果你像上面的答案一样使用 format_tb,你会得到更少的信息:

>>> tb_str = "".join(traceback.format_tb(exc.__traceback__))
>>> print("".join(tb_str))
File "<stdin>", line 2, in <module>