【问题标题】:Extract traceback info from an exception object从异常对象中提取回溯信息
【发布时间】:2012-07-10 00:32:47
【问题描述】:

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

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

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

我如何从 Exception 对象中提取回溯?

【问题讨论】:

    标签: python debugging exception-handling


    【解决方案1】:

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

    在 Python 3 中

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

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

    raise 文档中对这些功能进行了最低限度的描述。

    这部分答案的所有功劳都应归功于first posted this information 的维克多。我将它包括在这里只是因为这个答案停留在顶部,并且 Python 3 变得越来越普遍。

    在 Python 2 中

    这非常复杂。回溯的问题在于它们引用了堆栈帧,而堆栈帧引用了引用堆栈帧that have references to... 的回溯,你明白了。这会导致垃圾收集器出现问题。 (感谢ecatmur 首先指出这一点。)

    解决这个问题的好方法是在离开except 子句之后手术式地break the cycle,这是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
    

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

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

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

    【讨论】:

    【解决方案2】:

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

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

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

    【讨论】:

    • 这是错误的。 Python 3 确实将回溯对象放入异常中,如 e.__traceback__
    • @GlennMaynard Python 3 通过删除退出 except 块时的异常目标来解决该问题,每个 PEP 3110
    【解决方案3】:

    由于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__ 属性附加到它。

    所以我认为它应该被使用。

    【讨论】:

    • 是的,它是用来使用的。来自What’s New In Python 3.0“PEP 3134:异常对象现在将其回溯存储为 traceback 属性。这意味着异常对象现在包含与异常有关的所有信息,并且使用 sys 的理由更少.exc_info() (虽然后者没有被删除)。”
    • 我真的不明白为什么这个答案如此犹豫和模棱两可。这是一个记录在案的财产;为什么它是“被使用的”?
    • @MarkAmery 可能名称中的__ 表明它是一个实现细节,而不是公共属性?
    • @Basic 这不是这里所指的。按照惯例,在 Python 中 __foo 是私有方法,但 __foo__(也带有尾随下划线)是一种“神奇”方法(而不是私有)。
    • 仅供参考,__traceback__ 属性可以 100% 安全使用,但您喜欢使用,没有 GC 影响。从文档中很难看出这一点,但 ecatmur 找到了hard evidence
    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

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

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

      【讨论】:

      • 终于!这应该是最佳答案。谢谢你,丹尼尔!
      • 啊,在我发现这个之前,我花了最后 20 分钟试图弄清楚这个 :-) etype=type(exc) 现在可以省略顺便说一句:“在 3.5 版中更改:忽略 etype 参数并且从值的类型推断。” docs.python.org/3.7/library/… 在 Python 3.7.3 中测试。
      • 请不要存储exc请在我的回答末尾看到警告stackoverflow.com/a/67441882/5506988
      【解决方案6】:

      你可以使用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 超越基础 - 11 - 异常和错误 - Traceback 对象

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-12-22
        • 1970-01-01
        • 2018-11-11
        • 1970-01-01
        • 2020-03-08
        • 2021-12-25
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多