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

时间:2012-07-10 13:55:00

标签: python debugging exception-handling

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

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

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

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

6 个答案:

答案 0 :(得分:68)

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

在Python 3中

这很简单:异常配备了包含回溯的__traceback__属性。此属性也是可写的,可以使用with_traceback例外方法方便地设置:

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

这些功能最低限度地描述为raise文档的一部分。

答案的这一部分应归功于Vyctor,first posted this information。我把它包括在这里只是因为这个答案停留在顶部,而Python 3正变得越来越普遍。

在Python 2中

这很烦人。回溯的问题在于它们具有对堆栈帧的引用,并且堆栈帧具有对引用堆栈帧that have references to...的回溯的引用。这会导致垃圾收集器出现问题。 (感谢ecatmur首先指出这一点。)

解决这个问题的好方法是在离开{3}}子句后进行手术break the cycle,这就是Python 3所做的。 Python 2解决方案更加丑陋:为您提供了一个特殊功能sys.exc_info(),其中仅在 except 子句中有效。它返回一个包含异常,异常类型和当前正在处理的异常的回溯的元组。

因此,如果您在except子句中,则可以使用except的输出和traceback模块来执行各种有用的操作:

sys.exc_info()

但是,正如您的编辑所示,在已经处理完之后,如果您的例外尚未处理,那么您正试图获取 已经打印的追溯。这是一个更难的问题。不幸的是,>>> 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似乎很完美,但似乎只在交互式会话期间定义。

如果您可以控制引发异常的方式,则可以使用inspectcustom exception来存储某些信息。但我不完全确定这会如何起作用。

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

答案 1 :(得分:53)

由于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>

问题是,在Googling __traceback__一段时间之后,我发现只有少数文章,但没有一篇文章说明您是否应该(不)使用__traceback__

然而,Python 3 documentation for raise说:

  

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

所以我认为它意味着要使用。

答案 2 :(得分:13)

从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_exc

答案 3 :(得分:8)

有一个很好的理由,回溯没有存储在异常中;因为回溯保持对其堆栈本地的引用,这将导致循环引用和(临时)内存泄漏,直到循环GC启动。(这就是为什么你应该never store the traceback in a local variable。)

关于我能想到的唯一一件事就是你为monkeypatch stuff的全局变量,这样当它认为它正在捕捉Exception时它实际上捕获了一个特殊类型,并且异常传播给你来电者:

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

答案 4 :(得分:6)

顺便说一句,如果您希望像在终端上看到的那样获得完整的追溯,则需要这样做:

>>> 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>

答案 5 :(得分:2)

您可以使用 traceback.format_exc 返回 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 Beyond the Basics - 11 - 异常和错误 - 回溯对象