如何在`try`外获得带有`__traceback__`属性的异常,包含调用栈

3 投票
2 回答
88 浏览
提问于 2025-04-14 15:33

在Python(3.10)中,如果在一个try块里发生了异常,这个异常的追踪信息(traceback)并不会显示到调用这个try块的地方。这让我有点意外,更重要的是,这并不是我想要的结果。

下面是一个简单的程序,展示了这个问题:

# short_tb.py
import traceback


def caller():
    somefunction()


def somefunction():
    try:
        raise ValueError("This is a value error")
    except ValueError as e:
        # in my actual code, e is passed to a function, and __traceback__ is pulled from it for logging at this other
        # location; the code below simply demonstrates the lack of frames in the traceback object
        print("".join(traceback.format_tb(e.__traceback__)))


print("This will produce a traceback with only one frame, the one in somefunction()")
caller()


def caller2():
    somefunction2()


def somefunction2():
    raise ValueError("This is a value error")


print("This will produce a traceback with all relevant frames (the default behavior of the python interpreter)")
caller2()

输出结果:

This will produce a traceback with only one frame, the one in somefunction()
  File ".../short_tb.py", line 10, in somefunction
    raise ValueError("This is a value error")

This will produce a traceback with all relevant frames (the default behavior of the python interpreter)
Traceback (most recent call last):
  File ".../short_tb.py", line 30, in <module>
    caller2()
  File ".../short_tb.py", line 22, in caller2
    somefunction2()
  File ".../short_tb.py", line 26, in somefunction2
    raise ValueError("This is a value error")
ValueError: This is a value error

我希望__traceback__能包含第二个例子中的所有信息。我愿意覆盖这个变量,并且我可以在处理异常的地方这样做……但我该如何获取一个对象来实现这个目的呢?

在这个问题中,有很多关于追踪信息的回答,但似乎没有一个是关于traceback objects的:捕获并打印完整的Python异常追踪信息,而不停止/退出程序

2 个回答

1

正如@chepner在评论中提到的,“追踪记录是异常在被抛出和被捕获之间经过的所有步骤的记录,而不是异常被抛出时堆栈的状态。”不过,traceback模块也可以在代码的当前点生成堆栈帧,所以下面的代码会在一个辅助函数中打印出整个堆栈帧:

import traceback
import sys

def print_full_exception_stack():
    etype, e, tb = sys.exc_info()
    s = traceback.extract_stack()[:2]  # Frames up to where this helper function was called.
    t = traceback.extract_tb(tb)       # Frames to the exception.
    L = traceback.format_list(s + t)   # Join and format the frames.
    print('Traceback (most recent call last):')
    print(''.join(L).rstrip())
    print(f'{etype.__name__}: {e}')

def caller():
    somefunction()

def somefunction():
    try:
        raise ValueError('This is a value error')
    except ValueError:
        print_full_exception_stack()

caller()

输出:

Traceback (most recent call last):
  File "C:\Users\xxx\test.py", line 17, in <module>
    caller()
  File "C:\Users\xxx\test.py", line 4, in caller
    somefunction()
  File "C:\Users\xxx\test.py", line 8, in somefunction
    raise ValueError('This is a value error')
ValueError: This is a value error
1

更正确的处理方法已经由 @Mark Tolonen 提出,但如果真的需要 __traceback__ 包含完整的调用栈:

  • 可以使用 tb_frame.f_back 来访问之前的调用帧。
  • 使用 types.TracebackType 来重建 traceback 对象。
  • 通过 tb_next 这个构造参数将它们连接起来。
import traceback
import types


def caller(full = False):
    somefunction(full)

def somefunction(full):
    try:
        raise ValueError("This is a value error")
    except ValueError as e:
        if full:
            complete_traceback(e)
        print("".join(traceback.format_tb(e.__traceback__)))

def complete_traceback(exception):
    t = exception.__traceback__
    fb = t.tb_frame.f_back
    while fb:
        t = types.TracebackType(tb_next=t,
                                tb_frame=fb,
                                tb_lasti=fb.f_lasti,
                                tb_lineno=fb.f_lineno)
        fb = t.tb_frame.f_back
    exception.__traceback__ = t


print("This will produce a traceback with only one frame, the one in somefunction()")
caller()

print("This will produce a traceback with all relevant frames")
caller(True)

输出:

This will produce a traceback with only one frame, the one in somefunction()
  File ".../short_tb.py", line 10, in somefunction
    raise ValueError("This is a value error")

This will produce a traceback with all relevant frames
  File ".../short_tb.py", line 32, in <module>
    caller(True)
  File ".../short_tb.py", line 6, in caller
    somefunction(full)
  File ".../short_tb.py", line 10, in somefunction
    raise ValueError("This is a value error")

撰写回答