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


111

给定一个Exception对象(来源不明),有没有办法获取其回溯?我有这样的代码:

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

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

获得异常后,如何从Exception对象中提取回溯?

Answers:


92

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

在Python 3中

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

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

这些功能在raise文档中作了最少描述。

这部分答案应归功于Vyctor,后者首先发布了此信息。我之所以将其包含在此处,仅是因为此答案停留在顶部,并且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年

@ thg435,好的,那么这更有意义。考虑将我上面的解决方案sys.exc_info与我在其他问题上建议的回调方法结合使用。
senderle


69

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__为可写属性的情况下自动创建回溯对象。

因此,我认为它应该被使用。


4
是的,它应该被使用。从Python 3.0的新增功能开始, PEP 3134:异常对象现在将其回溯存储为traceback属性。这意味着异常对象现在包含与异常有关的所有信息,并且使用sys.exc_info()的原因更少了(尽管后者并未删除)。”
Maciej Szpakowski

我真的不明白为什么这个答案如此犹豫和模棱两可。这是有记录的财产;为什么它不是 “被使用”?
Mark Amery

2
@MarkAmery可能是__名称中的,表明它是实现的详细信息,而不是公共属性?
基本

4
@Basic并非此处所指示的内容。按照惯例,Python中__foo是私有方法,但是__foo__(也带有下划线)是“魔术”方法(不是私有)。
马克·阿默里

1
仅供参考,该__traceback__属性可以100%安全使用,但不影响GC。从文档中很难做到这一点,但是ecatmur找到了确凿的证据
senderle '17

38

一种从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


21

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

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

4
最后!这应该是最佳答案。谢谢,丹尼尔!
丹妮

3
Argh,我花了最后20分钟试图弄清楚这个问题,然后才发现它:-) etype=type(exc)现在可以省略了。顺便说一句:“在3.5版中已更改:etype参数将被忽略,并从值的类型推断出来。” docs.python.org/3.7/library/…在Python 3.7.3中进行了测试。
西罗Santilli郝海东冠状病六四事件法轮功

8

有一个很好的理由是,回溯不存储在异常中。因为回溯保留对堆栈本地的引用,所以这将导致循环引用和(临时)内存泄漏,直到循环GC启动。(这就是为什么永远不要将回溯存储在局部变量中的原因。)

关于我,我唯一想到的就是您可以进行修补 stuff的全局变量,以便当它认为正在捕获时Exception,实际上是在捕获特殊类型,并且异常作为调用者传播给您:

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

7
错了 Python 3确实将traceback对象放在异常中e.__traceback__
Glenn Maynard

6
@GlennMaynard Python 3通过except按照PEP 3110删除退出该块的异常目标解决了该问题。
ecatmur
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.