debugging 从异常对象提取跟踪信息

t9aqgxwy  于 2022-11-14  发布在  其他
关注(0)|答案(6)|浏览(112)

给定一个Exception对象(来源未知),有没有办法获得它的回溯?我有这样的代码:

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

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

一旦我有了异常对象,如何从异常对象中提取回溯?

t3irkdon

t3irkdon1#

由于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文档中的raise说明:
当引发例外状况时,追踪对象通常会自动建立,并以__traceback__属性(可写入)附加至该对象。
所以我想它应该是被利用的。

z6psavjg

z6psavjg2#

在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(...)将它们连接在一起。

qvtsj1bj

qvtsj1bj3#

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

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

lmyy7pcs4#

回溯不存储在异常中是有充分理由的;因为回溯保留了对堆栈局部变量的引用,所以这将导致循环引用和(临时)内存泄漏,直到循环GC开始。(这就是为什么您永远不应该将回溯存储在局部变量中的原因。)
我能想到的唯一方法就是monkeypatch stuff的全局变量,这样当它认为它正在捕获Exception时,它实际上是在捕获一个专用类型,并且异常会传播给作为调用方的您:

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

xpszyzbs5#

You could use traceback.format_exc which returns a str
or traceback.print_exc prints to stdout

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)

If you need to get it from the actual exception (although I don't see why)
traceback.format_exception returns a str
traceback.print_exception prints to stdout

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

Caution

Do not store a reference to __traceback__ (or exc ) for later use because the traceback object contains references to all the stack frame objects, which comprise the call stack, and each stack frame contains references to all of its local variables. As such, the size of the transitive closure of objects reachable from the traceback object can be very large. And if you maintain that reference, these objects will not be garbage‑collected. Prefer to render tracebacks into another form for even short‑term storage in memory."

  • Robert Smallshire - Python Beyond the Basics - 11 - Exceptions and Errors - Traceback objects*
h6my8fg2

h6my8fg26#

这个 问题 的 答案 取决 于 你 使用 的 Python 版本 。
在 Python 3 中
很 简单 :异常 带有 一 个 包含 回溯 的 __traceback__ 属性 。 这个 属性 也 是 可 写 的 , 可以 使用 异常 的 with_traceback 方法 方便 地 设置 :

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

中 的 每 一 个
这些 功能 至少 会 在 raise 文件 中 说明 。
这 部分 答案 的 所有 功劳 都 应该 归功 于 Vyctor , 他 是 first posted this information 。 我 在 这里 包括 它 只是 因为 这个 答案 停留 在 顶部 , 而 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 和 一 个 自 定义 异常 来 存储 一些 信息 , 但 我 不 完全 确定 这 是 如何 工作 的 。
说 实话 , 捕捉 并 返回 异常 是 一 件 不 寻常 的 事情 , 这 可能 是 一 个 迹象 , 表明 您 无论 如何 都 需要 重构 。

相关问题