143

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

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

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

拥有 Exception 对象后,如何从它中提取回溯?

4

6 回答 6

115

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

在 Python 3 中

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

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

这些功能作为raise文档的一部分进行了最低限度的描述。

这部分答案的所有功劳都应归功于首先发布此信息的维克多。我将它包括在这里只是因为这个答案停留在顶部,并且 Python 3 变得越来越普遍。

在 Python 2 中

这是令人讨厌的复杂。回溯的问题在于它们具有对堆栈框架的引用,而堆栈框架具有对具有对具有对...的引用的堆栈框架的引用的回溯的引用。你明白了。这会导致垃圾收集器出现问题。(感谢ecatmur首先指出这一点。)

解决这个问题的好方法是在离开子句后通过手术打破循环except,这就是 Python 3 所做的。Python 2 的解决方案要丑得多:为您提供了一个临时函数 ,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定义异常来存储一些信息。但我不完全确定这将如何工作。

说实话,捕获并返回异常是一件不寻常的事情。这可能表明您无论如何都需要重构。

于 2012-07-10T14:08:57.633 回答
91

Python 3.0 [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__.

但是,Python 3 文档raise说:

回溯对象通常在引发异常时自动创建并作为__traceback__可写属性附加到它。

所以我认为它应该被使用。

于 2013-01-28T14:31:59.560 回答
57

一种从 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(...)将它们连接在一起。如需更多参考,请访问:https ://docs.python.org/3/library/traceback.html#traceback.format_tb

于 2018-03-15T00:19:19.600 回答
35

顺便说一句,如果你想真正获得完整的回溯,就像你看到它打印到你的终端一样,你想要这个:

>>> 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>
于 2019-01-07T23:46:18.450 回答
9

回溯没有存储在异常中是有充分理由的;因为回溯保存对其堆栈局部变量的引用,这将导致循环引用和(临时)内存泄漏,直到循环 GC 启动。(这就是为什么您永远不应该将回溯存储在局部变量中。)

关于我能想到的唯一一件事就是让你使用 monkeypatchstuff的全局变量,这样当它认为它正在捕获时,Exception它实际上是在捕获一个专门的类型,并且异常作为调用者传播给你:

module_containing_stuff.Exception = type("BogusException", (Exception,), {})
try:
    stuff()
except Exception:
    import sys
    print sys.exc_info()
于 2012-07-10T16:00:44.430 回答
7

你可以使用traceback.format_excwhich 返回一个str

traceback.print_exc打印到标准输出

import traceback

try:
    b"x81".decode()
except UnicodeError:
    traceback.print_exc() # prints to stdout
    my_traceback = traceback.format_exc() # returns a str
print(my_traceback)

如果您需要从实际异常中获取它(尽管我不明白为什么)

traceback.format_exception返回一个str

traceback.print_exception打印到标准输出

import traceback

try:
    b"x81".decode()
except UnicodeError as exc:
    # etype is inferred from `value` since python3.5 so no need to pass a value...
    # format_exception returns a list
    my_traceback = "".join(traceback.format_exception(etype=None, value=exc, tb=exc.__traceback__))
    traceback.print_exception(etype=None, value=exc, tb=exc.__traceback__)

警告

不要存储对__traceback__(或exc)的引用以供以后使用,因为回溯对象包含对所有堆栈帧对象的引用,这些堆栈帧对象构成调用堆栈,并且每个堆栈帧都包含对其所有局部变量的引用。因此,可从回溯对象到达的对象的传递闭包的大小可能非常大。如果您维护该引用,这些对象将不会被垃圾回收。更喜欢将回溯呈现为另一种形式,以便在内存中进行短期存储。”

Robert Smallshire - Python 超越基础 - 11 - 异常和错误 - Traceback 对象

于 2021-05-07T21:30:10.447 回答