如何创建回溯对象

时间:2014-11-25 23:03:30

标签: python traceback

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

<traceback object at 0x7f6575c37e48>

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

5 个答案:

答案 0 :(得分:12)

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

traceback模块中的所有功能都不会创建它们。您当然可以将类型设置为types.TracebackType,但如果您调用其构造函数,则只需获得TypeError: cannot create 'traceback' instances

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


但是,您可以访问堆栈帧,而模拟回溯所需的其他所有内容都是微不足道的。您甚至可以编写一个包含tb_frametb_lastitb_linenotb_next属性的类(使用您可以从traceback.extract_stack获取的信息和其中一个inspect函数),它看起来就像是对任何纯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.

答案 1 :(得分:5)

如果你真的需要欺骗另一个库 - 特别是用C语言编写并使用非公共API - 有两种可能的方法来获得真正的回溯对象。我没有让任何一个人可靠地工作。此外,两者都是特定于CPython的,不仅要求使用C API层,而且要求使用随时可能发生变化的未记录的类型和功能,并提供新的和令人兴奋的机会来解析您的解释器。但是如果你想尝试,它们可能对一开始就很有用。


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

PyTraceBack_Here

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

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

现在,对于CPython的正常64位版本,p8 = ctypes.cast(id(tb), ctypes.POINTER(ctypes.c_ulong)) p4 = ctypes.cast(id(tb), ctypes.POINTER(ctypes.c_uint)) / p8[:2]是正常的对象头,之后是特定于回溯的字段,因此p4[:4]p8[3]tb_framep4[8]分别是p4[9]tb_lasti。所以:

tb_lineno

但接下来的部分有点困难,因为p4[8], p4[9] = tb.tb_frame.f_lasti, tb.tb_frame.f_lineno 实际上不是tb_frame,它只是一个原始PyObject *,所以关闭转到frameobject.h,你看到它真的是struct _frame *所以你可以再次使用相同的技巧。在重新分配PyFrameObject *以指向_ctypes.Py_INCREF之后,或者在您尝试打印回溯时,请记住Py_DECREF框架的下一帧和p8[3]框架本身ll段错误并失去你写完这篇文章的所有工作。 :)

答案 2 :(得分: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)

相关文档在这里:

答案 3 :(得分:0)

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

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中列出的更全面的解决方案感兴趣。

答案 4 :(得分:0)

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

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