繁体   English   中英

从compile()获取包括SyntaxError在内的回溯信息

[英]Get traceback information including SyntaxError from compile()

基本问题

似乎SyntaxError S(和TypeError由凸起或多个) compile()函数中包括由返回的堆栈跟踪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的最佳方法是什么? 是否需要使用filenameSyntaxError异常对象本身手动构造最后一个list元素(即表示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)如果typeSyntaxError并且value具有适当的格式,它将打印出现语法错误的行,并使用插入符指示错误的大致位置。

SyntaxError文档

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

具有语法错误的行不包含在回溯中仅是逻辑的; 无法执行具有语法错误的代码,因此不会为其创建任何执行帧。 而最常见的执行框架是compile()函数抛出异常。

因此,你坚持你的“丑陋”方法; 它是处理SyntaxError异常的正确方法。 但是,属性记录在案。

注意, exception.message通常设置为exception.args[0] ,而str(exception) 通常会给你相同的消息(如果args更长,你得到str(exception.args) ,尽管有些异常类型提供了自定义__str__通常只给你exception.args[0] )。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM