【问题标题】:File "<string>" traceback with line preview带有行预览的文件“<string>”回溯
【发布时间】:2017-11-08 15:16:58
【问题描述】:

我正在通过exec 制作几个函数,这可能会出错。但是,当 Python 出错时,它不会显示发生错误的行。

例如使用:

fn_str = '''\
def fn():
    raise Exception()
'''

globs = {}
exec(fn_str, globs)
fn = globs['fn']

fn()

给我们输出:

Traceback (most recent call last):
  File "...", line 10, in <module>
    fn()
  File "<string>", line 2, in fn
Exception

但是,如果我们不要使用 eval。然后我们得到程序出错的那一行:

def fn():
    raise Exception()

fn()
Traceback (most recent call last):
  File "...", line 4, in <module>
    fn()
  File "...", line 2, in fn
    raise Exception()
Exception

我已经研究过使用__traceback__,但是我找不到添加到“文件”行下的回溯的方法。所以我能得到的最好的结果是:

fn_str = '''\
def fn():
    try:
        raise Exception()
    except BaseException as e:
        tb = e.__traceback__
        if 1 <= tb.tb_lineno <= len(fn_lines):
            e.args = ((e.args[0] if e.args else '') + ' - ' + fn_lines[tb.tb_lineno - 1].strip(),)
        raise
'''

globs = {'fn_lines': fn_str.split('\n')}
exec(fn_str, globs)
fn = globs['fn']

fn()
Traceback (most recent call last):
  File "...", line 16, in <module>
    fn()
  File "<string>", line 3, in fn
Exception:  - raise Exception()

这样做的最大问题是如果eval 调用其他代码,那么- raise Exception() 的来源就会变得混乱。


有没有办法让 eval 代码提供它出错的行?

【问题讨论】:

    标签: python python-3.x traceback


    【解决方案1】:

    缺少的行是 Python 无法在磁盘上找到名为 &lt;string&gt; 的文件的症状,该文件是您编译的 sn-p 代码中内置的文件名。 (虽然如果你创建一个完全具有该名称的文件,Python 会从中打印行!)

    方法 1. 您可以自己捕获异常,无论是在应用程序的顶层还是其他地方,而不是让默认的内置回溯例程触发,您可以调用标准库例程 traceback.print_exc()它从标准库模块linecache 中提取行。因为linecache 缓存是一个简单的公共 Python 字典,您可以使用它需要打印的源代码预先填充它。见:

    Why does the Python linecache affect the traceback module but not regular tracebacks?

    生成的代码:

    import linecache
    import traceback
    
    source = 'print("Hello, world" + 1)'
    source_name = 'Example'
    lines = source.splitlines(True)
    linecache.cache[source_name] = len(source), None, lines, source_name
    compiled = compile(source, source_name, 'exec')
    try:
        eval(compiled)
    except Exception:
        traceback.print_exc()
    

    方法 2. 您还可以通过自己负责打印异常来避免间接填充全局缓存:您可以让 Python 将回溯数据作为元组列表返回,单步执行他们添加缺少的行,然后像往常一样最后打印它们。

    这是一个fill_in_lines() 函数,它在一个打印完整回溯的小程序中用缺失的信息填充回溯:

    import sys
    import traceback
    
    def fill_in_lines(frames, source_name, source):
        lines = source.splitlines()
        for filename, line_number, function_name, text in frames:
            if filename == source_name:
                text = lines[line_number - 1]
            yield filename, line_number, function_name, text
    
    source = 'print("Hello, world" + 1)'
    source_name = 'Example'
    compiled = compile(source, source_name, 'exec')
    try:
        eval(compiled)
    except Exception as e:
        _, _, tb = sys.exc_info()
        frames = traceback.extract_tb(tb)
        frames = fill_in_lines(frames, source_name, source)
    
        print('Traceback (most recent call last):')
        print(''.join(traceback.format_list(frames)), end='')
        print('{}: {}'.format(type(e).__name__, str(e)))
    

    我可以在这里使用花哨的名称“示例”,因为我使用compile() 设置它。在您的情况下,您可能希望将裸字符串 '&lt;string&gt;' 作为 source_name 参数传递。

    【讨论】:

    • 有趣。我已经验证这可行,但我要睡觉了。谢谢你的回答,我明天再研究一下
    • 我很高兴它成功了!今天早上醒来时,我想,“那么,当我预先填充行缓存时,为什么 没有 工作”,并进行了快速搜索,并找到了已经解决该主题的 Stack Overflow 答案。不知道为什么我昨天没有找到它!我已经更新了我的答案以包含该方法的链接。
    【解决方案2】:

    这是vaultahdeleted answer的转发。

    当解释器无法找到源代码时会发生这种情况 无论出于何种原因,这条线。内置模块就是这种情况, 编译的文件,exec 字符串等。更具体地说,在回溯中 你可以看到fn的代码对象的文件名设置为 &lt;string&gt;

    File "<string>", line 2, in fn
    

    因为&lt;string&gt; 不是有效的文件名,所以对 源代码丢失。

    一种选择是创建一个临时文件,在其中写入fn_str, compile fn_str 设置文件名,执行编译后的代码,然后 最后调用函数。请注意,您需要保持文件处于活动状态 至少在源行被回溯打印缓存之前 设施

    from tempfile import NamedTemporaryFile
    import traceback
    
    with NamedTemporaryFile('w') as temp:
        code = compile(fn_str, temp.name, 'exec')
        print(fn_str, file=temp, flush=True)
        globs = {}
        exec(code, globs)
        fn = globs['fn']
        try:
            fn()
        except:
            traceback.print_exc()
    

    打印

    Traceback (most recent call last):
      File "test.py", line 16, in <module>
        fn()
      File "/tmp/tmp9q2bogm6", line 2, in fn
        raise Exception()
    Exception
    

    由于我们已经创建了一个“真实”文件,我们可以委托 编译执行代码到runpy.run_path

    from tempfile import NamedTemporaryFile
    import runpy, traceback
    
    with NamedTemporaryFile('w') as temp:
        print(fn_str, file=temp, flush=True)
        fn = runpy.run_path(temp.name)['fn']
        try:
            fn()
        except:
            traceback.print_exc()
    

    【讨论】:

      猜你喜欢
      • 2015-01-27
      • 2019-05-22
      • 2014-02-06
      • 2014-10-25
      • 1970-01-01
      • 2011-02-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多