从更高级别提出异常,la警告

时间:2015-12-09 09:28:55

标签: python python-3.x python-3.5

在模块警告(https://docs.python.org/3.5/library/warnings.html)中,有能力发出似乎来自堆栈早期某处的警告:

warnings.warn('This is a test', stacklevel=2)

是否有相应的提出错误?我知道我可以使用备用回溯引发错误,但我无法在模块中创建该回溯,因为它需要来自之前的版本。我想象的是:

tb = magic_create_traceback_right_here()
raise ValueError('This is a test').with_traceback(tb.tb_next)

原因是我正在开发一个具有函数module.check_raise的模块,我想要引发一个错误,该错误似乎源自调用该函数的位置。如果我在module.check_raise函数中引发错误,则它似乎来自module.check_raise内,这是不受欢迎的。

另外,我尝试了一些技巧,比如提出一个虚拟异常,捕获它,然后传递回溯,但不知怎的,tb_next变成了None。我没有想法。

修改

我想要这个最小例子的输出(称为tb2.py):

import check_raise

check_raise.raise_if_string_is_true('True')

只有这个:

Traceback (most recent call last):
  File "tb2.py", line 10, in <module>
    check_raise.raise_if_string_is_true(string)
RuntimeError: An exception was raised.

3 个答案:

答案 0 :(得分:2)

如果我理解正确,你会想要这个最小例子的输出:

def check_raise(function):
    try:
        return function()
    except Exception:
        raise RuntimeError('An exception was raised.')

def function():
    1/0

check_raise(function)

只有这个:

Traceback (most recent call last):
  File "tb2.py", line 10, in <module>
    check_raise(function)
RuntimeError: An exception was raised.

事实上,它的输出要多得多;存在异常链接,可以通过立即处理RuntimeError,删除其__context__并重新提升它来处理,并且RuntimeError本身还有另一行追溯:

  File "tb2.py", line 5, in check_raise
    raise RuntimeError('An exception was raised.')

据我所知,纯Python代码不可能在引发异常后替换异常的回溯;解释器可以控制添加它,但只有在处理异常时它才会公开当前的回溯。没有API(甚至在使用跟踪函数时也没有)将自己的回溯传递给解释器,并且回溯对象是不可变的(这是由涉及C级别的Jinja hack所解决的)。

因此,进一步假设您对缩短的追溯感兴趣不是为了进一步的程序化使用,而是仅针对用户友好的输出,您最好的选择是excepthook来控制如何将回溯打印到安慰。为了确定停止打印的位置,可以使用一个特殊的局部变量(这比将回溯限制为长度减去1或者这样更加强大)。此示例需要Python 3.5(适用于traceback.walk_tb):

import sys
import traceback

def check_raise(function):
    __exclude_from_traceback_from_here__ = True
    try:
        return function()
    except Exception:
        raise RuntimeError('An exception was raised.')

def print_traceback(exc_type, exc_value, tb):
    for i, (frame, lineno) in enumerate(traceback.walk_tb(tb)):
        if '__exclude_from_traceback_from_here__' in frame.f_code.co_varnames:
            limit = i
            break
    else:
        limit = None
    traceback.print_exception(
        exc_type, exc_value, tb, limit=limit, chain=False)

sys.excepthook = print_traceback

def function():
    1/0

check_raise(function)

这是现在的输出:

Traceback (most recent call last):
  File "tb2.py", line 26, in <module>
    check_raise(function)
RuntimeError: An exception was raised.

答案 1 :(得分:2)

我不敢相信我发布了这个

通过这样做,您将反对the zen

  

特殊情况不足以打破规则。

但如果你坚持这里是你的神奇代码。

check_raise.py

import sys
import traceback

def raise_if_string_is_true(string):
    if string == 'true':
        #the frame that called this one
        f = sys._getframe().f_back
        #the most USELESS error message ever
        e = RuntimeError("An exception was raised.")

        #the first line of an error message
        print('Traceback (most recent call last):',file=sys.stderr)
        #the stack information, from f and above
        traceback.print_stack(f)
        #the last line of the error
        print(*traceback.format_exception_only(type(e),e),
              file=sys.stderr, sep="",end="")

        #exit the program
        #if something catches this you will cause so much confusion
        raise SystemExit(1)
        # SystemExit is the only exception that doesn't trigger an error message by default.

这是纯python,不会干扰sys.excepthook,即使在except Exception:中,它也不会被except:捕获

test.py

import check_raise

check_raise.raise_if_string_is_true("true")
print("this should never be printed")

将为您提供您想要的(可怕的无信息和极其伪造)追溯信息。

Tadhgs-MacBook-Pro:Documents Tadhg$ python3 test.py
Traceback (most recent call last):
  File "test.py", line 3, in <module>
    check_raise.raise_if_string_is_true("true")
RuntimeError: An exception was raised.
Tadhgs-MacBook-Pro:Documents Tadhg$

答案 2 :(得分:0)

编辑:之前的版本没有提供引用或解释。

我建议在动机中提及PEP 3134

  

有时,故意对异常处理程序有用       重新提出异常,或提供额外信息或       将异常转换为另一种类型。 __cause__属性       提供了一种记录异常直接原因的明确方法。

当使用Exception属性引发__cause__时,回溯消息采用以下形式:

Traceback (most recent call last):
 <CAUSE TRACEBACK>

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  <MAIN TRACEBACK>

据我所知,这正是你想要完成的事情;清楚地表明错误的原因不是你的模块,而是其他地方。如果你试图像你的编辑那样试图忽略追溯信息,那么这个答案的其余部分对你没有任何帮助。

关于语法的说明:

  

始终初始化异常对象上的__cause__属性       没有。它由'raise'语句的新形式设置:

   raise EXCEPTION from CAUSE
     

相当于:

    exc = EXCEPTION
    exc.__cause__ = CAUSE
    raise exc

所以最基本的例子就是这样:

def function():
    int("fail")

def check_raise(function):
    try:
        function()
    except Exception as original_error:
        err = RuntimeError("An exception was raised.")
        raise err from original_error

check_raise(function)

,它会显示如下错误消息:

Traceback (most recent call last):
  File "/PATH/test.py", line 7, in check_raise
    function()
  File "/PATH/test.py", line 3, in function
    int("fail")
ValueError: invalid literal for int() with base 10: 'fail'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/PATH/test.py", line 12, in <module>
    check_raise(function)
  File "/PATH/test.py", line 10, in check_raise
    raise err from original_error
RuntimeError: An exception was raised.

然而,原因的第一行是try check_raise区块中的陈述:

  File "/PATH/test.py", line 7, in check_raise
    function()

所以在提出err之前,可能(或可能不)希望从original_error移除最外层的追溯框架:

except Exception as original_error:
    err = RuntimeError("An exception was raised.")
    original_error.__traceback__ = original_error.__traceback__.tb_next
    raise err from original_error

这样,回溯中看似来自check_raise的唯一一行是最后一个raise语句,使用纯python代码无法省略,尽管取决于您可以制作的消息的信息量很明显,你的模块不是问题的原因:

err = RuntimeError("""{0.__qualname__} encountered an error during call to {1.__module__}.{1.__name__}
the traceback for the error is shown above.""".format(function,check_raise))

像这样引发异常的好处是,当引发新错误时,原始的Traceback消息不会丢失,这意味着可以引发一系列非常复杂的异常,并且python仍将正确显示所有相关信息:< / p>

def check_raise(function):
    try:
        function()
    except Exception as original_error:
        err = RuntimeError("""{0.__qualname__} encountered an error during call to {1.__module__}.{1.__name__}
the traceback for the error is shown above.""".format(function,check_raise))
        original_error.__traceback__ = original_error.__traceback__.tb_next
        raise err from original_error

def test_chain():
    check_raise(test)

def test():
    raise ValueError

check_raise(test_chain)

给我以下错误消息:

Traceback (most recent call last):
  File "/Users/Tadhg/Documents/test.py", line 16, in test
    raise ValueError
ValueError

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/Tadhg/Documents/test.py", line 13, in test_chain
    check_raise(test)
  File "/Users/Tadhg/Documents/test.py", line 10, in check_raise
    raise err from original_error
RuntimeError: test encountered an error during call to __main__.check_raise
the traceback for the error is shown above.

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/Tadhg/Documents/test.py", line 18, in <module>
    check_raise(test_chain)
  File "/Users/Tadhg/Documents/test.py", line 10, in check_raise
    raise err from original_error
RuntimeError: test_chain encountered an error during call to __main__.check_raise
the traceback for the error is shown above.

是的,它很长但是显着更具信息性:

Traceback (most recent call last):
  File "/Users/Tadhg/Documents/test.py", line 18, in <module>
    check_raise(test_chain)
RuntimeError: An exception was raised.

更不用说即使程序没有结束,原始错误仍然可用:

import traceback

def check_raise(function):
    ...

def fail():
    raise ValueError

try:
    check_raise(fail)
except RuntimeError as e:
    cause = e.__cause__
    print("check_raise failed because of this error:")
    traceback.print_exception(type(cause), cause, cause.__traceback__)

print("and the program continues...")