Python“raise from”;使用

在Python中raiseraise from有什么区别?

try:
raise ValueError
except Exception as e:
raise IndexError

的收益率

Traceback (most recent call last):
File "tmp.py", line 2, in <module>
raise ValueError
ValueError


During handling of the above exception, another exception occurred:


Traceback (most recent call last):
File "tmp.py", line 4, in <module>
raise IndexError
IndexError

而且

try:
raise ValueError
except Exception as e:
raise IndexError from e

的收益率

Traceback (most recent call last):
File "tmp.py", line 2, in <module>
raise ValueError
ValueError


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


Traceback (most recent call last):
File "tmp.py", line 4, in <module>
raise IndexError from e
IndexError
135180 次浏览

不同之处在于,当你使用from时,__cause__属性被设置,并且消息声明异常是直接由。如果省略from,则没有设置__cause__,但也可以设置__context__属性,然后回溯将上下文显示为在处理过程中发生了其他事情

如果在异常处理程序中使用了raise,则会设置__context__;如果你在其他地方使用raise,也没有设置__context__

如果设置了__cause__,则在异常上也设置了__suppress_context__ = True标志;当__suppress_context__被设置为True时,打印回溯时将忽略__context__

当从异常处理程序引发时,你想要显示上下文(不想要在处理过程中发生了另一个异常消息),然后使用raise ... from None__suppress_context__设置为True

换句话说,Python在异常上设置了上下文,这样你就可以自省异常是在哪里引发的,让你看看是否有另一个异常被它替换了。你也可以在一个异常中添加导致,使另一个异常的回溯显式(使用不同的措辞),上下文被忽略(但在调试时仍然可以自省)。使用raise ... from None可以抑制正在打印的上下文。

参见raise语句文档:

from子句用于异常链接:如果给出,第二个表达式必须是另一个异常类或实例,然后它将作为__cause__属性附加到引发的异常(可写)。如果引发的异常没有被处理,两个异常都将被打印:

>>> try:
...     print(1 / 0)
... except Exception as exc:
...     raise RuntimeError("Something bad happened") from exc
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ZeroDivisionError: int division or modulo by zero


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


Traceback (most recent call last):
File "<stdin>", line 4, in <module>
RuntimeError: Something bad happened

如果在异常处理程序或finally子句中引发异常,则类似的机制隐式工作:前一个异常随后作为新异常的__context__属性附加:

>>> try:
...     print(1 / 0)
... except:
...     raise RuntimeError("Something bad happened")
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ZeroDivisionError: int division or modulo by zero


During handling of the above exception, another exception occurred:


Traceback (most recent call last):
File "<stdin>", line 4, in <module>
RuntimeError: Something bad happened

也可以查看内置异常文档来获取上下文和异常附加原因信息的详细信息。

PEP 3134,异常链接和嵌入式跟踪引入了异常链(隐式地与显式raise EXCEPTION或隐式raise连接,显式地与显式raise EXCEPTION from CAUSE连接)。下面是相关段落来理解它们的用法:

动机

在处理一个异常(异常A)的过程中,可能会出现另一个异常(异常B)。在今天的Python(2.4版)中,如果发生这种情况,异常B将向外传播,异常A将丢失。为了调试问题,了解这两个异常是很有用的。__context__属性自动保留此信息。

有时,异常处理程序有意地重新引发异常是有用的,可以提供额外的信息,也可以将异常转换为另一种类型。__cause__属性提供了一种显式的方式来记录异常的直接原因。

[…]

隐式异常链接

下面是一个例子来说明__context__属性:

def compute(a, b):
try:
a/b
except Exception, exc:
log(exc)


def log(exc):
file = open('logfile.txt')  # oops, forgot the 'w'
print >>file, exc
file.close()

调用compute(0, 0)会导致ZeroDivisionError. c。compute()函数捕捉到这个异常并调用log(exc),但是log()函数在试图写入未打开的文件时也会引发异常。

在今天的Python中,compute()的调用者会抛出IOErrorZeroDivisionError丢失。通过建议的更改,IOError的实例有了一个额外的__context__属性,该属性保留了ZeroDivisionError

[…]

显式异常链接

异常对象上的__cause__属性总是初始化为None。它是由raise语句的新形式设置的:

raise EXCEPTION from CAUSE

这相当于:

exc = EXCEPTION
exc.__cause__ = CAUSE
raise exc

在下面的示例中,数据库提供了几种不同类型存储的实现,其中文件存储是一种。数据库设计者希望错误以DatabaseError对象的形式传播,这样客户端不必知道特定于存储的细节,但又不想丢失底层错误信息。

class DatabaseError(Exception):
pass


class FileDatabase(Database):
def __init__(self, filename):
try:
self.file = open(filename)
except IOError, exc:
raise DatabaseError('failed to open') from exc

如果对open()的调用引发异常,问题将被报告为DatabaseError,其中__cause__属性揭示了IOError作为原始原因。

增强的报告

默认异常处理程序将被修改以报告链式异常。异常链通过跟随__cause____context__属性来遍历,其中__cause__优先。为了与回溯的时间顺序保持一致,最近引发的异常显示在最后;也就是说,显示从最内层异常的描述开始,并将链备份到最外层异常。这些回溯的格式和往常一样,有如下一行:

上述异常是导致以下异常的直接原因:

在处理上述异常时,发生了另一个异常:

这取决于它们分别是通过__cause__还是__context__链接。以下是该程序的梗概:

def print_chain(exc):
if exc.__cause__:
print_chain(exc.__cause__)
print '\nThe above exception was the direct cause...'
elif exc.__context__:
print_chain(exc.__context__)
print '\nDuring handling of the above exception, ...'
print_exc(exc)

[…]

PEP 415,使用异常属性实现上下文抑制随后引入了异常上下文的抑制(使用显式的raise EXCEPTION from None)。下面是相关段落来理解它的用法:

建议

将引入BaseException上的一个新属性__suppress_context__。当__cause__被设置时,__suppress_context__将被设置为True。特别地,raise exc from cause语法将把exc.__suppress_context__设置为True。异常打印代码将检查该属性,以确定是否打印上下文和原因。__cause__将回到它最初的目的和值。

具有print_line_and_file异常属性的__suppress_context__有优先级。

总之,raise exc from cause将等价于:

exc.__cause__ = cause
raise exc

其中exc.__cause__ = cause隐式设置exc.__suppress_context__

因此,在PEP 415中,PEP 3134中给出的程序草图变成如下:

def print_chain(exc):
if exc.__cause__:
print_chain(exc.__cause__)
print '\nThe above exception was the direct cause...'
elif exc.__context__ and not exc.__suppress_context__:
print_chain(exc.__context__)
print '\nDuring handling of the above exception, ...'
print_exc(exc)

最简短的回答pep - 3134说明了一切。raise Exception from e设置新异常的__cause__字段。

一个更长的答案来自相同的PEP:

  • __context__字段将隐式地设置为except:块中的原始错误,除非用__suppress_context__ = True告知不要这样做。
  • __cause__就像context一样,但必须使用from语法显式设置
  • 当你在except块中调用raise时,traceback将始终链起。你可以通过以下方法摆脱traceback: a)吞下一个异常except: pass或直接打乱sys.exc_info()

长话短说

import traceback
import sys


class CustomError(Exception):
def __init__(self):
super().__init__("custom")


def print_exception(func):
print(f"\n\n\nEXECURTING FUNCTION '{func.__name__}' \n")
try:
func()
except Exception as e:
"Here is result of our actions:"
print(f"\tException type:    '{type(e)}'")
print(f"\tException message: '{e}'")
print(f"\tException context: '{e.__context__}'")
print(f"\tContext type:      '{type(e.__context__)}'")
print(f"\tException cause:   '{e.__cause__}'")
print(f"\tCause type:         '{type(e.__cause__)}'")
print("\nTRACEBACKSTART>>>")
traceback.print_exc()
print("<<<TRACEBACKEND")




def original_error_emitter():
x = {}
print(x.does_not_exist)


def vanilla_catch_swallow():
"""Nothing is expected to happen"""
try:
original_error_emitter()
except Exception as e:
pass


def vanilla_catch_reraise():
"""Nothing is expected to happen"""
try:
original_error_emitter()
except Exception as e:
raise e


def catch_replace():
"""Nothing is expected to happen"""
try:
original_error_emitter()
except Exception as e:
raise CustomError()


def catch_replace_with_from():
"""Nothing is expected to happen"""
try:
original_error_emitter()
except Exception as e:
raise CustomError() from e


def catch_reset_trace():
saw_an_error = False
try:
original_error_emitter()
except Exception as e:
saw_an_error = True
if saw_an_error:
raise CustomError()


print("Note: This will print nothing")
print_exception(vanilla_catch_swallow)
print("Note: This will print AttributeError and 1 stack trace")
print_exception(vanilla_catch_reraise)
print("Note: This will print CustomError with no context but 2 stack traces")
print_exception(catch_replace)
print("Note: This will print CustomError with AttributeError context and 2 stack traces")
print_exception(catch_replace_with_from)
print("Note: This will brake traceback chain")
print_exception(catch_reset_trace)

将导致以下输出:

Note: This will print nothing
EXECURTING FUNCTION 'vanilla_catch_swallow'








Note: This will print AttributeError and 1 stack trace
EXECURTING FUNCTION 'vanilla_catch_reraise'


Exception type:    '<class 'AttributeError'>'
Exception message: ''dict' object has no attribute 'does_not_exist''
Exception context: 'None'
Context type:      '<class 'NoneType'>'
Exception cause:   'None'
Cause type:         '<class 'NoneType'>'


TRACEBACKSTART>>>
Traceback (most recent call last):
File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 11, in print_exception
func()
File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 41, in vanilla_catch_reraise
raise e
File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 39, in vanilla_catch_reraise
original_error_emitter()
File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 27, in original_error_emitter
print(x.does_not_exist)
AttributeError: 'dict' object has no attribute 'does_not_exist'
<<<TRACEBACKEND






Note: This will print CustomError with no context but 2 stack traces
EXECURTING FUNCTION 'catch_replace'


Exception type:    '<class '__main__.CustomError'>'
Exception message: 'custom'
Exception context: ''dict' object has no attribute 'does_not_exist''
Context type:      '<class 'AttributeError'>'
Exception cause:   'None'
Cause type:         '<class 'NoneType'>'


TRACEBACKSTART>>>
Traceback (most recent call last):
File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 46, in catch_replace
original_error_emitter()
File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 27, in original_error_emitter
print(x.does_not_exist)
AttributeError: 'dict' object has no attribute 'does_not_exist'


During handling of the above exception, another exception occurred:


Traceback (most recent call last):
File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 11, in print_exception
func()
File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 48, in catch_replace
raise CustomError()
CustomError: custom
<<<TRACEBACKEND






Note: This will print CustomError with AttributeError context and 2 stack traces
EXECURTING FUNCTION 'catch_replace_with_from'


Exception type:    '<class '__main__.CustomError'>'
Exception message: 'custom'
Exception context: ''dict' object has no attribute 'does_not_exist''
Context type:      '<class 'AttributeError'>'
Exception cause:   ''dict' object has no attribute 'does_not_exist''
Cause type:         '<class 'AttributeError'>'


TRACEBACKSTART>>>
Traceback (most recent call last):
File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 53, in catch_replace_with_from
original_error_emitter()
File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 27, in original_error_emitter
print(x.does_not_exist)
AttributeError: 'dict' object has no attribute 'does_not_exist'


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


Traceback (most recent call last):
File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 11, in print_exception
func()
File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 55, in catch_replace_with_from
raise CustomError() from e
CustomError: custom
<<<TRACEBACKEND






Note: This will brake traceback chain
EXECURTING FUNCTION 'catch_reset_trace'


Exception type:    '<class '__main__.CustomError'>'
Exception message: 'custom'
Exception context: 'None'
Context type:      '<class 'NoneType'>'
Exception cause:   'None'
Cause type:         '<class 'NoneType'>'


TRACEBACKSTART>>>
Traceback (most recent call last):
File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 11, in print_exception
func()
File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 64, in catch_reset_trace
raise CustomError()
CustomError: custom
<<<TRACEBACKEND