基本问题
似乎compile()
函数引发的SyntaxError
s(和TypeError
s(不包括在sys.exc_info()
返回的堆栈跟踪中,而是使用traceback.print_exc
作为格式化输出的一部分打印。
例
例如,给定以下代码(其中filename
是包含带有行$ flagrant syntax error
的 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:
('<scriptname>', 6, 'exec_file', 'code = compile(f.read(), filename, "exec")')
using traceback.print_exc:
Traceback (most recent call last):
File "<scriptname>", line 6, in exec_file
code = compile(f.read(), filename, "exec")
File "<filename>", line 3
$ flagrant syntax error
^
SyntaxError: invalid syntax
我的问题
我有三个问题:
- 为什么
sys.exc_info()
回溯不包括生成SyntaxError
的帧? traceback.print_exc
如何获取丢失的帧信息?
将- "提取的"回溯信息(包括
SyntaxError
(保存在列表中的最佳方法是什么?最后一个列表元素(即来自表示SyntaxError
发生位置的堆栈帧的信息(是否需要使用filename
和SyntaxError
异常对象本身手动构造?
示例用例
对于上下文,这是我尝试获得完整堆栈跟踪提取的用例。
我有一个程序,它基本上通过exec
一些包含用户编写的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 行。
两种方法之间的唯一区别是print_exc()
打印格式化的异常。对于包括设置该异常中信息的格式的SyntaxError
,其中包括导致问题的实际行。
对于回溯本身,print_exc()
使用sys.exc_info()[2]
,与您已经用于生成回溯的信息相同。换句话说,它不会获得比你已经得到的更多的信息,但你忽略了异常信息本身:
>>> import traceback
>>> try:
... compile('Hmmm, nope!', '<stdin>', 'exec')
... except SyntaxError as e:
... print ''.join(traceback.format_exception_only(type(e), e))
...
File "<stdin>", line 1
Hmmm, nope!
^
SyntaxError: invalid syntax
此处traceback.format_exception_only()
是traceback.print_exc()
用来设置异常值格式的未记录函数。所有信息都在那里供您提取异常本身:
>>> dir(e)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__getitem__', '__getslice__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '__unicode__', 'args', 'filename', 'lineno', 'message', 'msg', 'offset', 'print_file_and_line', 'text']
>>> e.args
('invalid syntax', ('<stdin>', 1, 11, 'Hmmm, nope!n'))
>>> e.filename, e.lineno, e.offset, e.text
('<stdin>', 1, 11, 'Hmmm, nope!n')
另请参阅traceback.print_exception()
的文档:
(3( 如果 type 为
SyntaxError
并且 value 具有适当的格式,则打印发生语法错误的行,并带有指示错误大致位置的插入符号。
和SyntaxError
文档:
此类的实例具有属性
filename
、lineno
、offset
和text
,以便于访问详细信息。 异常实例str()
仅返回消息。
回溯中不包含语法错误的行只是合乎逻辑的;无法执行带有语法错误的代码,因此从未为其创建执行帧。异常是由compile()
函数引发的,该函数是最底部的执行帧。
因此,你被困在"丑陋"的方法上;这是处理SyntaxError
异常的正确方法。但是,记录了这些属性。
请注意,exception.message
通常设置为 exception.args[0]
,并且str(exception)
通常会为您提供相同的消息(如果args
更长,则改为str(exception.args)
,尽管某些异常类型提供了通常只为您提供exception.args[0]
的自定义__str__
(。