Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/315.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
引发异常时,如何修改Python回溯对象?_Python_Traceback - Fatal编程技术网

引发异常时,如何修改Python回溯对象?

引发异常时,如何修改Python回溯对象?,python,traceback,Python,Traceback,我正在开发一个Python库,第三方开发人员使用它为我们的核心应用程序编写扩展 我想知道在引发异常时是否可以修改回溯,因此最后一个堆栈帧是开发人员代码中对库函数的调用,而不是引发异常的库中的行。在堆栈的底部还有一些框架,其中包含对首次加载代码时使用的函数的引用,理想情况下,我也想删除这些函数 提前感谢您的建议 看看jinja2在这里做什么: 这很难看,但它似乎做了你需要做的事情。我不会在这里复制粘贴示例,因为它很长。不更改回溯怎么样?你所要求的两件事都可以用不同的方式更容易地完成 如果在开发人

我正在开发一个Python库,第三方开发人员使用它为我们的核心应用程序编写扩展

我想知道在引发异常时是否可以修改回溯,因此最后一个堆栈帧是开发人员代码中对库函数的调用,而不是引发异常的库中的行。在堆栈的底部还有一些框架,其中包含对首次加载代码时使用的函数的引用,理想情况下,我也想删除这些函数


提前感谢您的建议

看看jinja2在这里做什么:


这很难看,但它似乎做了你需要做的事情。我不会在这里复制粘贴示例,因为它很长。

不更改回溯怎么样?你所要求的两件事都可以用不同的方式更容易地完成

  • 如果在开发人员的代码中捕获了库中的异常,并引发了新的异常,那么当然会抛出原始的回溯。这就是通常处理异常的方式。。。如果您只允许引发原始异常,但通过咀嚼它来删除所有“上部”帧,那么实际的异常将没有意义,因为回溯中的最后一行本身无法引发异常
  • 要去掉最后几帧,您可以请求缩短回溯。。。像traceback.print_exception()这样的函数使用一个“limit”参数,可以用来跳过最后几个条目

  • 这就是说,如果你真的需要的话,你应该很有可能咀嚼回溯。。。但是你会在哪里做呢?如果在一些最顶层的包装器代码中,那么您可以简单地获取回溯,获取一个片段以删除您不想要的部分,然后使用“回溯”模块中的函数按需要格式化/打印。

    您可能也感兴趣,它是在Python3中实现的,允许您将一个异常/回溯到上游异常


    这与修改回溯并不完全相同,但它可能是向库用户传达“短版本”的理想方式,同时仍然有“长版本”可用。

    您可以通过使用回溯的下一个元素tbu轻松删除回溯的顶部:

    except:
        ei = sys.exc_info()
        raise ei[0], ei[1], ei[2].tb_next
    

    tb_next是一个只读属性,所以我不知道如何从底部删除内容。您可能可以使用properties机制来允许访问属性,但我不知道如何做到这一点。

    您可能会对这段代码感兴趣

    它进行回溯并删除第一个文件,该文件不应显示。然后模拟Python行为:

    Traceback (most recent call last):
    
    仅当回溯包含多个文件时才会显示。 这看起来就像我的额外框架不在那里一样

    这里是我的代码,假设有一个字符串
    text

    try:
        exec(text)
    except:
        # we want to format the exception as if no frame was on top.
        exp, val, tb = sys.exc_info()
        listing = traceback.format_exception(exp, val, tb)
        # remove the entry for the first frame
        del listing[1]
        files = [line for line in listing if line.startswith("  File")]
        if len(files) == 1:
            # only one file, remove the header.
            del listing[0]
        print("".join(listing), file=sys.stderr)
        sys.exit(1)
    

    对于python3,这里是我的答案。请阅读评论以获取解释:

    def pop_exception_traceback(exception,n=1):
        #Takes an exception, mutates it, then returns it
        #Often when writing my repl, tracebacks will contain an annoying level of function calls (including the 'exec' that ran the code)
        #This function pops 'n' levels off of the stack trace generated by exception
        #For example, if print_stack_trace(exception) originally printed:
        #   Traceback (most recent call last):
        #   File "<string>", line 2, in <module>
        #   File "<string>", line 2, in f
        #   File "<string>", line 2, in g
        #   File "<string>", line 2, in h
        #   File "<string>", line 2, in j
        #   File "<string>", line 2, in k
        #Then print_stack_trace(pop_exception_traceback(exception),3) would print: 
        #   File "<string>", line 2, in <module>
        #   File "<string>", line 2, in j
        #   File "<string>", line 2, in k
        #(It popped the first 3 levels, aka f g and h off the traceback)
        for _ in range(n):
            exception.__traceback__=exception.__traceback__.tb_next
        return exception
    
    def pop_异常_回溯(异常,n=1):
    #获取异常,对其进行变异,然后返回它
    #通常在编写我的repl时,回溯将包含令人讨厌的函数调用级别(包括运行代码的“exec”)
    #此函数从异常生成的堆栈跟踪中弹出“n”级
    #例如,如果最初打印的是打印\堆栈\跟踪(异常):
    #回溯(最近一次呼叫最后一次):
    #文件“”,第2行,在
    #文件“”,第2行,在f中
    #文件“”,第2行,g中
    #文件“”,第2行,h中
    #文件“”,第2行,在j中
    #文件“”,第2行,单位为k
    #然后打印\u堆栈\u跟踪(pop\u异常\u回溯(异常),3)将打印:
    #文件“”,第2行,在
    #文件“”,第2行,在j中
    #文件“”,第2行,单位为k
    #(它弹出了前3个级别,也就是f g和h)
    对于范围内的u(n):
    异常.\uuuu回溯\uuuu=异常.\uuu回溯\uuuu.tb\u下一步
    返回异常
    
    从Python 3.7开始,您可以实例化一个新的
    回溯
    对象,并在抛出时使用
    .with\u traceback()
    方法。下面是一些演示代码,它使用了
    sys.\u getframe(1)
    (或更健壮的替代方法),在使调试器相信在
    myassert(False)
    中发生了错误的同时,会引发
    AssertionError
    sys.\u getframe(1)
    忽略顶部堆栈帧

    我应该补充的是,虽然这在调试器中看起来很好,但控制台行为揭示了它的真正作用:

    Traceback (most recent call last):
      File ".\test.py", line 35, in <module>
        myassert_false()
      File ".\test.py", line 31, in myassert_false
        myassert(False)
      File ".\test.py", line 26, in myassert
        raise AssertionError().with_traceback(back_tb)
      File ".\test.py", line 31, in myassert_false
        myassert(False)
    AssertionError
    

    在我写这篇文章时,上面的链接给出了一个404错误。这是当前的链接:我投了反对票,原因如下:这个答案提供了很好的建议,但实际上并没有回答这个问题。@BryanOakley:如果建议非常相关,如果建议太大而无法放入评论中,那么它就属于答案。要么是这样,要么让知识远离这样,这将是非常可悲的。所以我认为在这种情况下,否决权是不合适的。@max:我想你是对的。我发现自己也在回答类似这样的问题——“你可以这么做,但为什么……这里还有一些事情需要考虑”。谢谢你让我负责。如果可以的话,我会改变我的投票。我必须不同意这样的论点:“[这]没有意义,因为回溯中的最后一行本身无法引发异常”。取
    a+=1
    行。它看起来不“能够”引发异常(不包含单词
    raise
    )。但是,如果
    a
    类型为
    str
    ,那么它会这样做,并且异常是有意义的。现在假设
    a
    是某个自定义类的一个实例,带有
    \uuuu iadd\uuuu
    ,就像
    str
    ,操作数是
    int
    是没有意义的。使用相同的语法,回溯现在将指向更深的位置,而不是错误所在的行
    """Modify traceback on exception.
    
    See also https://github.com/python/cpython/commit/e46a8a
    """
    
    import sys
    import types
    
    
    def myassert(condition):
        """Throw AssertionError with modified traceback if condition is False."""
        if condition:
            return
    
        # This function ... is not guaranteed to exist in all implementations of Python.
        # https://docs.python.org/3/library/sys.html#sys._getframe
        # back_frame = sys._getframe(1)
        try:
            raise AssertionError
        except AssertionError:
            traceback = sys.exc_info()[2]
            back_frame = traceback.tb_frame.f_back
    
        back_tb = types.TracebackType(tb_next=None,
                                      tb_frame=back_frame,
                                      tb_lasti=back_frame.f_lasti,
                                      tb_lineno=back_frame.f_lineno)
        raise AssertionError().with_traceback(back_tb)
    
    
    def myassert_false():
        """Test myassert(). Debugger should point at the next line."""
        myassert(False)
    
    
    if __name__ == "__main__":
        myassert_false()