问题:从异常对象中提取回溯信息
给定一个Exception对象(来源不明),有没有办法获取其回溯?我有这样的代码:
def stuff():
try:
.....
return useful
except Exception as e:
return e
result = stuff()
if isinstance(result, Exception):
result.traceback <-- How?
获得异常后,如何从Exception对象中提取回溯?
回答 0
这个问题的答案取决于您使用的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
和自定义异常来存储某些信息。但是我不完全确定那将如何工作。
实话实说,捕获并返回异常是一件不寻常的事情。这可能表明您仍然需要进行重构。
回答 1
从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__
为可写属性的情况下自动创建回溯对象。
因此,我认为它应该被使用。
回答 2
一种从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
回答 3
顺便说一句,如果您希望像在终端上看到的那样真正获得完整的追溯,则需要这样做:
>>> 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
有一个很好的理由是,回溯不存储在异常中。因为回溯保留对堆栈本地的引用,所以这将导致循环引用和(临时)内存泄漏,直到循环GC启动。(这就是为什么永远不要将回溯存储在局部变量中的原因。)
关于我,我唯一想到的就是您可以进行修补 stuff
的全局变量,以便当它认为正在捕获时Exception
,实际上是在捕获特殊类型,并且异常作为调用者传播给您:
module_containing_stuff.Exception = type("BogusException", (Exception,), {})
try:
stuff()
except Exception:
import sys
print sys.exc_info()