我正在研究第三方开发人员使用的Python库,为我们的核心应用程序编写扩展。
我想知道在引发异常时是否可以修改回溯,因此最后一个堆栈框架是在开发人员代码中调用库函数,而不是引发异常的库中的行。堆栈底部还有一些框架,其中包含对第一次加载我想要删除的代码时使用的函数的引用。
提前感谢任何建议!
答案 0 :(得分:11)
您可以通过使用traceback的tb_next元素进行提升来轻松删除回溯的顶部:
except:
ei = sys.exc_info()
raise ei[0], ei[1], ei[2].tb_next
tb_next是一个read_only属性,所以我不知道从底部删除东西的方法。您可能可以使用属性机制来允许访问该属性,但我不知道该怎么做。
答案 1 :(得分:8)
看看jinja2在这里做了什么:
https://github.com/mitsuhiko/jinja2/blob/5b498453b5898257b2287f14ef6c363799f1405a/jinja2/debug.py
这很难看,但似乎做了你需要做的事情。我不会在这里复制粘贴示例,因为它很长。
答案 2 :(得分:1)
如何不改变追溯?您要求的两件事情都可以通过不同的方式更轻松地完成。
那就是说,如果你真的需要,那么应该很有可能进行回溯......但是你会在哪里做呢?如果在最高级别的某些包装器代码中,那么您可以简单地抓取回溯,取一个切片来移除您不想要的部件,然后使用“traceback”模块中的函数来根据需要进行格式化/打印。 / p>
答案 3 :(得分:1)
您可能也对PEP-3134感兴趣,它在python 3中实现,并允许您将一个异常/回溯添加到上游异常中。
这与修改回溯并不完全相同,但它可能是向库用户传达“短版本”的理想方式,同时仍然可以使用“长版”。
答案 4 :(得分:1)
从Python 3.7开始,可以实例化一个新的traceback
对象,并在抛出时使用.with_traceback()
方法。以下是一些演示代码,它们使用sys._getframe(1)
(或更健壮的替代方法)引发AssertionError
,同时使调试器相信myassert(False)
中发生的错误:sys._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
我没有删除堆栈的顶部,而是添加了倒数第二帧的副本。
无论如何,我专注于调试器的行为,看来这是正确的:
"""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()
答案 5 :(得分:0)
此代码可能对您有用。
它需要一个回溯并删除第一个不应显示的文件。然后它模拟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)
答案 6 :(得分:0)
对于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