从 compile() 中获取回溯信息,包括语法错误



基本问题

似乎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发生位置的堆栈帧的信息(是否需要使用 filenameSyntaxError异常对象本身手动构造?

示例用例

对于上下文,这是我尝试获得完整堆栈跟踪提取的用例。

我有一个程序,它基本上通过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( 如果 typeSyntaxError 并且 value 具有适当的格式,则打印发生语法错误的行,并带有指示错误大致位置的插入符号。

SyntaxError文档:

此类的实例具有属性 filenamelinenooffsettext,以便于访问详细信息。 异常实例str()仅返回消息。

回溯中不包含语法错误的行只是合乎逻辑的;无法执行带有语法错误的代码,因此从未为其创建执行帧。异常是由compile()函数引发的,该函数是最底部的执行帧。

因此,你被困在"丑陋"的方法上;这是处理SyntaxError异常的正确方法。但是,记录了这些属性。

请注意,exception.message通常设置为 exception.args[0] ,并且str(exception)通常会为您提供相同的消息(如果args更长,则改为str(exception.args),尽管某些异常类型提供了通常只为您提供exception.args[0]的自定义__str__(。

相关内容

最新更新