【问题标题】:How to create a traceback object如何创建回溯对象
【发布时间】:2014-11-25 23:03:29
【问题描述】:

我想创建一个类似于 sys.exc_info()[2] 返回的回溯。我不想要行列表,我想要一个实际的回溯对象:

<traceback object at 0x7f6575c37e48>

我该怎么做?我的目标是让它包含当前堆栈减去一帧,因此看起来调用者是最近的调用。

【问题讨论】:

  • 你想要这个回溯做什么? traceback.extract_stack 和/或inspect.stack 的结果不是traceback 对象,但它也不仅仅是行列表。其中之一就足够了吗?
  • 第三方库只接受回溯对象,我不想破解它。
  • “仅接受回溯对象”是否意味着“检查 isinstance(t, types.TracebackType)?如果是这样……好吧,如果 traceback 是少数不允许的类型之一,我不会感到惊讶本身被用作基础,但我至少要先检查一下。如果没有,是什么意思

标签: python traceback


【解决方案1】:

从 Python 3.7 开始,您可以从 Python 动态创建回溯对象。
要创建与 raise 创建的相同的回溯:

raise Exception()

使用这个:

import sys
import types

def exception_with_traceback(message):
    tb = None
    depth = 0
    while True:
        try:
            frame = sys._getframe(depth)
            depth += 1
        except ValueError as exc:
            break

        tb = types.TracebackType(tb, frame, frame.f_lasti, frame.f_lineno)

    return Exception(message).with_traceback(tb)

相关文档在这里:

【讨论】:

  • 这太好了,谢谢。
【解决方案2】:

没有记录的方式来创建回溯对象。

traceback 模块中的任何函数都不会创建它们。您当然可以使用types.TracebackType 访问该类型,但如果您调用它的构造函数,您只会得到TypeError: cannot create 'traceback' instances

原因是回溯包含对内部的引用,您实际上无法从 Python 中访问或生成这些引用。


但是,您可以访问堆栈帧,而模拟回溯所需的其他一切都是微不足道的。您甚至可以编写一个具有tb_frametb_lastitb_linenotb_next 属性的类(使用您可以从traceback.extract_stackinspect 函数之一获得的信息),看起来完全一样就像任何纯 Python 代码的回溯一样。

So there's a good chance that whatever you really want to do is doable, even though what you're asking for is not.

【讨论】:

  • Jinja2 做了这个 hack 加上一些更复杂的事情。 github.com/mitsuhiko/jinja2/blob/…
  • @digenishjkl 那里有一些不错的东西。特别是,在添加额外功能的同时动态代理到真实的回溯对于许多用例来说显然是一个好主意。我早该想到的……
  • 在pytest中构建调试器时也很有用,可以过滤掉__tracebackhide__帧。
【解决方案3】:

“为了更好地支持堆栈跟踪的动态创建,types.TracebackType 现在可以从 Python 代码中实例化,并且 tracebacks 上的 tb_next 属性现在是可写的。”

这里(python 3.7)https://docs.python.org/3/library/types.html#types.TracebackType有一个解释(在python 3.7中)

【讨论】:

    【解决方案4】:

    如果你真的需要欺骗另一个库——尤其是用 C 语言编写并使用非公共 API 的库——有两种可能的方法来获取真正的回溯对象。我还没有让任何一个可靠地工作。此外,两者都是 CPython 特定的,不仅需要使用 C API 层,还需要使用可能随时更改的未记录类型和函数,并为解释器提供新的和令人兴奋的机会来进行分段错误。但是,如果您想尝试,它们可能对您有用。


    PyTraceBack 类型不是公共 API 的一部分。但是(除了在 Python 目录而不是 Object 目录中定义)它是作为 C API 类型构建的,只是没有记录。因此,如果您查看您的 Python 版本的 traceback.htraceback.c,您会发现……嗯,没有 PyTraceBack_New,但是 PyTraceBack_Here 构造了一个新的回溯并将其交换到当前的异常信息中。我不确定调用它是否有效,除非当前存在异常,并且如果存在 is 当前异常,您可能会通过像这样对其进行变异来搞砸它,但需要进行一些试验和崩溃或阅读代码,希望你可以让它工作:

    import ctypes
    import sys
    
    ctypes.pythonapi.PyTraceBack_Here.argtypes = (ctypes.py_object,)
    ctypes.pythonapi.PyTraceBack_Here.restype = ctypes.c_int
    
    def _fake_tb():
        try:
            1/0
        except:
            frame = sys._getframe(2)
            if ctypes.pythonapi.PyTraceBack_Here(frame):
                raise RuntimeError('Oops, probably hosed the interpreter')
            raise
    
    def get_tb():
        try:
            _fake_tb()
        except ZeroDivisionError as e:
           return e.__traceback__
    

    作为一个有趣的替代方案,我们可以尝试动态改变回溯对象。要获取回溯对象,只需引发并捕获异常:

    try: 1/0
    except exception as e: tb = e.__traceback__ # or sys.exc_info()[2]
    

    唯一的问题是它指向你的堆栈帧,而不是你的调用者的,对吧?如果回溯是可变的,您可以轻松解决:

    tb.tb_lasti, tb.tb_lineno = tb.tb_frame.f_lasti, tb.tb_frame.f_lineno
    tb.tb_frame = tb.tb_frame.f_back
    

    也没有设置这些东西的方法。请注意,它没有setattro,它的getattro 是通过动态构建__dict__ 来工作的,所以显然我们获得这些东西的唯一方法是通过底层结构。您应该使用 ctypes.Structure 真正构建它,但作为快速破解:

    p8 = ctypes.cast(id(tb), ctypes.POINTER(ctypes.c_ulong))
    p4 = ctypes.cast(id(tb), ctypes.POINTER(ctypes.c_uint))
    

    现在,对于普通的 64 位 CPython 构建,p8[:2] / p4[:4] 是普通的对象头,然后是特定于回溯的字段,所以 p8[3]tb_frame,而 @ 987654341@ 和 p4[9] 分别是 tb_lastitb_lineno。所以:

    p4[8], p4[9] = tb.tb_frame.f_lasti, tb.tb_frame.f_lineno
    

    但是下一部分有点难,因为tb_frame 实际上不是PyObject *,它只是一个原始的struct _frame *,所以你转到frameobject.h,在那里你会发现它确实是一个PyFrameObject * 所以你可以再次使用相同的技巧。在重新分配p8[3] 以指向pf8[3] 后,请记住_ctypes.Py_INCREF 帧的下一帧和Py_DECREF 帧本身,或者一旦您尝试打印回溯,您就会出现段错误并丢失所有工作已经写完了。 :)

    【讨论】:

      【解决方案5】:

      正如其他人所指出的,不可能创建回溯对象。但是,您可以编写自己的具有相同属性的类:

      from collections import namedtuple
      fake_tb = namedtuple('fake_tb', ('tb_frame', 'tb_lasti', 'tb_lineno', 'tb_next'))
      

      您仍然可以将此类的实例传递给某些 Python 函数。最值得注意的是traceback.print_exception(...),它产生与 Python 的标准异常钩子相同的输出。

      如果您(像我一样)因为正在开发基于 PyQt 的 GUI 应用程序而遇到此问题,您可能还对this blog post 中列出的更全面的解决方案感兴趣。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-12-08
        • 2012-02-27
        • 2016-09-05
        • 2020-12-09
        • 2011-01-02
        • 2013-04-06
        相关资源
        最近更新 更多