基本问题
看来,compile()函数引发的SyntaxErrors(和TypeErrors)不包含在sys.exc_info()返回的堆栈跟踪中,而是使用traceback.print_exc作为格式化输出的一部分打印.
例
例如,给定以下代码(其中filename是包含带有$flagrant语法错误行的Python代码的文件的名称):
import sys
from traceback import extract_tb
try:
with open(filename) as f:
code = compile(f.read(), filename, "exec")
except:
print "using sys.exc_info:"
tb_list = extract_tb(sys.exc_info()[2])
for f in tb_list:
print f
print "using traceback.print_exc:"
from traceback import print_exc
print_exc()
我得到以下输出(其中< scriptname>是包含上述代码的脚本的名称):
using sys.exc_info:
('', 6, 'exec_file', 'code = compile(f.read(), filename, "exec")')
using traceback.print_exc:
Traceback (most recent call last):
File "", line 6, in exec_file
code = compile(f.read(), filename, "exec")
File "", line 3
$flagrant syntax error
^
SyntaxError: invalid syntax
我的问题
我有三个问题:
>为什么sys.exc_info()的回溯不包括生成SyntaxError的帧?
> traceback.print_exc如何获取缺少的帧信息?
>在列表中保存“提取的”回溯信息(包括SyntaxError)的最佳方法是什么?是否需要使用filename和SyntaxError异常对象本身手动构造最后一个列表元素(即表示发生SyntaxError的堆栈帧的信息)?
用例示例
对于上下文,这是我试图获得完整的堆栈跟踪提取的用例.
我有一个程序,通过执行包含用户编写的Python代码的一些文件实质上实现DSL. (无论这是否是一个良好的DSL实现策略,我或多或少地坚持它.)当遇到用户代码中的错误时,我会(在某些情况下)像解释器一样保存错误以后,而不是呕吐堆栈痕迹和死亡.所以我有专门用来存储这些信息的ScriptExcInfo类.这是该类的__init__方法(稍微编辑过的版本),完成上述问题的相当丑陋的解决方法:
def __init__(self, scriptpath, add_frame):
self.exc, tb = sys.exc_info()[1:]
self.tb_list = traceback.extract_tb(tb)
if add_frame:
# Note: I'm pretty sure that the names of the linenumber and
# message attributes are undocumented, and I don't think there's
# actually a good way to access them.
if isinstance(exc, TypeError):
lineno = -1
text = exc.message
else:
lineno = exc.lineno
text = exc.text
# '?' is used as the function name since there's no function context
# in which the SyntaxError or TypeError can occur.
self.tb_list.append((scriptpath, lineno, '?', text))
else:
# Pop off the frames related to this `exec` infrastructure.
# Note that there's no straightforward way to remove the unwanted
# frames for the above case where the desired frame was explicitly
# constructed and appended to tb_list, and in fact the resulting
# list is out of order (!!!!).
while scriptpath != self.tb_list[-1][0]:
self.tb_list.pop()
请注意,“相当难看”,我的意思是这个解决方法将应该是单参数的4行__init__函数转换为需要两个参数并占用13行.