引发异常时,如何修改Python回溯对象?
我正在开发一个Python库,第三方开发人员使用它为我们的核心应用程序编写扩展 我想知道在引发异常时是否可以修改回溯,因此最后一个堆栈帧是开发人员代码中对库函数的调用,而不是引发异常的库中的行。在堆栈的底部还有一些框架,其中包含对首次加载代码时使用的函数的引用,理想情况下,我也想删除这些函数引发异常时,如何修改Python回溯对象?,python,traceback,Python,Traceback,我正在开发一个Python库,第三方开发人员使用它为我们的核心应用程序编写扩展 我想知道在引发异常时是否可以修改回溯,因此最后一个堆栈帧是开发人员代码中对库函数的调用,而不是引发异常的库中的行。在堆栈的底部还有一些框架,其中包含对首次加载代码时使用的函数的引用,理想情况下,我也想删除这些函数 提前感谢您的建议 看看jinja2在这里做什么: 这很难看,但它似乎做了你需要做的事情。我不会在这里复制粘贴示例,因为它很长。不更改回溯怎么样?你所要求的两件事都可以用不同的方式更容易地完成 如果在开发人
提前感谢您的建议 看看jinja2在这里做什么:
这很难看,但它似乎做了你需要做的事情。我不会在这里复制粘贴示例,因为它很长。不更改回溯怎么样?你所要求的两件事都可以用不同的方式更容易地完成
这就是说,如果你真的需要的话,你应该很有可能咀嚼回溯。。。但是你会在哪里做呢?如果在一些最顶层的包装器代码中,那么您可以简单地获取回溯,获取一个片段以删除您不想要的部分,然后使用“回溯”模块中的函数按需要格式化/打印。您可能也感兴趣,它是在Python3中实现的,允许您将一个异常/回溯到上游异常
这与修改回溯并不完全相同,但它可能是向库用户传达“短版本”的理想方式,同时仍然有“长版本”可用。您可以通过使用回溯的下一个元素tbu轻松删除回溯的顶部:
except:
ei = sys.exc_info()
raise ei[0], ei[1], ei[2].tb_next
tb_next是一个只读属性,所以我不知道如何从底部删除内容。您可能可以使用properties机制来允许访问属性,但我不知道如何做到这一点。您可能会对这段代码感兴趣 它进行回溯并删除第一个文件,该文件不应显示。然后模拟Python行为:
Traceback (most recent call last):
仅当回溯包含多个文件时才会显示。
这看起来就像我的额外框架不在那里一样
这里是我的代码,假设有一个字符串text
:
try:
exec(text)
except:
# we want to format the exception as if no frame was on top.
exp, val, tb = sys.exc_info()
listing = traceback.format_exception(exp, val, tb)
# remove the entry for the first frame
del listing[1]
files = [line for line in listing if line.startswith(" File")]
if len(files) == 1:
# only one file, remove the header.
del listing[0]
print("".join(listing), file=sys.stderr)
sys.exit(1)
对于python3,这里是我的答案。请阅读评论以获取解释:
def pop_exception_traceback(exception,n=1):
#Takes an exception, mutates it, then returns it
#Often when writing my repl, tracebacks will contain an annoying level of function calls (including the 'exec' that ran the code)
#This function pops 'n' levels off of the stack trace generated by exception
#For example, if print_stack_trace(exception) originally printed:
# Traceback (most recent call last):
# File "<string>", line 2, in <module>
# File "<string>", line 2, in f
# File "<string>", line 2, in g
# File "<string>", line 2, in h
# File "<string>", line 2, in j
# File "<string>", line 2, in k
#Then print_stack_trace(pop_exception_traceback(exception),3) would print:
# File "<string>", line 2, in <module>
# File "<string>", line 2, in j
# File "<string>", line 2, in k
#(It popped the first 3 levels, aka f g and h off the traceback)
for _ in range(n):
exception.__traceback__=exception.__traceback__.tb_next
return exception
def pop_异常_回溯(异常,n=1):
#获取异常,对其进行变异,然后返回它
#通常在编写我的repl时,回溯将包含令人讨厌的函数调用级别(包括运行代码的“exec”)
#此函数从异常生成的堆栈跟踪中弹出“n”级
#例如,如果最初打印的是打印\堆栈\跟踪(异常):
#回溯(最近一次呼叫最后一次):
#文件“”,第2行,在
#文件“”,第2行,在f中
#文件“”,第2行,g中
#文件“”,第2行,h中
#文件“”,第2行,在j中
#文件“”,第2行,单位为k
#然后打印\u堆栈\u跟踪(pop\u异常\u回溯(异常),3)将打印:
#文件“”,第2行,在
#文件“”,第2行,在j中
#文件“”,第2行,单位为k
#(它弹出了前3个级别,也就是f g和h)
对于范围内的u(n):
异常.\uuuu回溯\uuuu=异常.\uuu回溯\uuuu.tb\u下一步
返回异常
从Python 3.7开始,您可以实例化一个新的回溯
对象,并在抛出时使用.with\u traceback()
方法。下面是一些演示代码,它使用了sys.\u getframe(1)
(或更健壮的替代方法),在使调试器相信在myassert(False)
中发生了错误的同时,会引发AssertionError
:sys.\u getframe(1)
忽略顶部堆栈帧
我应该补充的是,虽然这在调试器中看起来很好,但控制台行为揭示了它的真正作用:
Traceback (most recent call last):
File ".\test.py", line 35, in <module>
myassert_false()
File ".\test.py", line 31, in myassert_false
myassert(False)
File ".\test.py", line 26, in myassert
raise AssertionError().with_traceback(back_tb)
File ".\test.py", line 31, in myassert_false
myassert(False)
AssertionError
在我写这篇文章时,上面的链接给出了一个404错误。这是当前的链接:我投了反对票,原因如下:这个答案提供了很好的建议,但实际上并没有回答这个问题。@BryanOakley:如果建议非常相关,如果建议太大而无法放入评论中,那么它就属于答案。要么是这样,要么让知识远离这样,这将是非常可悲的。所以我认为在这种情况下,否决权是不合适的。@max:我想你是对的。我发现自己也在回答类似这样的问题——“你可以这么做,但为什么……这里还有一些事情需要考虑”。谢谢你让我负责。如果可以的话,我会改变我的投票。我必须不同意这样的论点:“[这]没有意义,因为回溯中的最后一行本身无法引发异常”。取
a+=1
行。它看起来不“能够”引发异常(不包含单词raise
)。但是,如果a
类型为str
,那么它会这样做,并且异常是有意义的。现在假设a
是某个自定义类的一个实例,带有\uuuu iadd\uuuu
,就像str
,操作数是int
是没有意义的。使用相同的语法,回溯现在将指向更深的位置,而不是错误所在的行
"""Modify traceback on exception.
See also https://github.com/python/cpython/commit/e46a8a
"""
import sys
import types
def myassert(condition):
"""Throw AssertionError with modified traceback if condition is False."""
if condition:
return
# This function ... is not guaranteed to exist in all implementations of Python.
# https://docs.python.org/3/library/sys.html#sys._getframe
# back_frame = sys._getframe(1)
try:
raise AssertionError
except AssertionError:
traceback = sys.exc_info()[2]
back_frame = traceback.tb_frame.f_back
back_tb = types.TracebackType(tb_next=None,
tb_frame=back_frame,
tb_lasti=back_frame.f_lasti,
tb_lineno=back_frame.f_lineno)
raise AssertionError().with_traceback(back_tb)
def myassert_false():
"""Test myassert(). Debugger should point at the next line."""
myassert(False)
if __name__ == "__main__":
myassert_false()