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

geo*_*org 95 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?
Run Code Online (Sandbox Code Playgroud)

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

sen*_*rle 78

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

在Python 3中

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

raise Exception("foo occurred").with_traceback(tracebackobj)
Run Code Online (Sandbox Code Playgroud)

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

这部分答案的所有功劳都归功于Vyctor,他首先发布了这些信息.我把它包括在这里只是因为这个答案停留在顶部,而Python 3正变得越来越普遍.

在Python 2中

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

解决这个问题的好方法是在离开子句后手术打破循环except,这就是Python 3所做的.Python 2解决方案更加丑陋:您可以使用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
Run Code Online (Sandbox Code Playgroud)

但是,随着您的编辑表示,你正在试图获得该回溯,如果你的异常没有被处理的已打印,它之后已经被处理.这是一个更难的问题.不幸的是,没有处理异常时sys.exc_info返回(None, None, None).其他相关sys属性也无济于事.sys.exc_traceback在没有处理异常时被弃用和未定义; sys.last_traceback看起来很完美,但似乎只能在交互式会话期间定义.

如果你能控制异常怎么提高,你也许能够使用inspect自定义异常来存储一些信息.但我不完全确定这是如何工作的.

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


Vyk*_*tor 60

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>
Run Code Online (Sandbox Code Playgroud)

问题是,谷歌搜索__traceback__了一段时间后,我发现只有少数文章,但没有一个描述你是否应该(不)使用__traceback__.

但是,Python 3文档raise说:

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

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

  • 是的,它意味着被使用.从[Python 3.0中的新功能](https://docs.python.org/3.5/whatsnew/3.0.html)"PEP 3134:异常对象现在将它们的回溯存储为__traceback__属性.这意味着现在包含一个异常对象所有与异常有关的信息,使用sys.exc_info()的原因较少(尽管后者未被删除)." (4认同)
  • @MarkAmery 可能是名称中的 `__` 表示它是一个实现细节,而不是一个公共属性? (3认同)
  • @Basic这不是它在这里所表明的.传统上在Python中,`__ foo`是一个私有方法,但`__foo__`(也带有尾随下划线)是一种"魔术"方法(而不是私有方法). (3认同)
  • 仅供参考,“__traceback__”属性可以 100% 安全地使用,没有 GC 影响。从文档中很难看出这一点,但 ecatmur 找到了[确凿的证据](/sf/ask/799042611/#comment19173332_11417308)。 (2认同)

Hie*_*ieu 21

从Python 3中的异常对象获取回溯作为字符串的方法:

import traceback

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

traceback.format_tb(...)返回字符串列表.''.join(...)将他们联系在一起.如需更多参考,请访问:https://docs.python.org/3/library/traceback.html#traceback.format_exc


Mar*_*ark 11

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

\n

traceback.print_exc打印到标准输出

\n
import traceback\n\ntry:\n    b"x81".decode()\nexcept UnicodeError:\n    traceback.print_exc() # prints to stdout\n    my_traceback = traceback.format_exc() # returns a str\nprint(my_traceback)\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

traceback.format_exception返回一个str

\n

traceback.print_exception打印到标准输出

\n
import traceback\n\ntry:\n    b"x81".decode()\nexcept UnicodeError as exc:\n    # etype is inferred from `value` since python3.5 so no need to pass a value...\n    # format_exception returns a list\n    my_traceback = "".join(traceback.format_exception(etype=None, value=exc, tb=exc.__traceback__))\n    traceback.print_exception(etype=None, value=exc, tb=exc.__traceback__)\n
Run Code Online (Sandbox Code Playgroud)\n

警告

\n
\n

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

\n
\n

Robert Smallshire - Python 超越基础知识 - 11 - 异常和错误 - 回溯对象

\n


Dan*_*ous 10

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

>>> 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
Run Code Online (Sandbox Code Playgroud)

如果您使用format_tb上述答案,则建议您获得的信息较少:

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

  • 最后!这应该是最佳答案。谢谢,丹尼尔! (2认同)
  • Argh,我花了最后20分钟试图弄清楚这个问题,然后才发现:-)`etype = type(exc)`现在可以省略了。顺便说一句:“在3.5版中已更改:etype参数被忽略并从价值类型。” https://docs.python.org/3.7/library/traceback.html#traceback.print_exception在Python 3.7.3中进行了测试。 (2认同)

eca*_*mur 8

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

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

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

  • 这是错的.Python 3确实将traceback对象放在异常中,如`e .__ traceback__`. (7认同)
  • @GlennMaynard Python 3通过根据[PEP 3110](http://www.python.org/dev/peps/pep-3110/#semantic-changes)删除退出`except`块的异常目标来解决该问题. (5认同)