Python 中 "raise from" 的用法
在Python中,raise
和raise 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
3 个回答
最简单的回答。PEP-3134 说得很清楚。使用 raise Exception from e
可以把新异常的 __cause__
字段设置为原来的异常。
更详细的回答来自同一个PEP:
__context__
字段会在except:
块中自动设置为原始错误,除非你特别指定__suppress_context__ = True
来不显示这个上下文。__cause__
和上下文类似,但需要你用from
语法来明确设置。- 当你在
except
块中调用raise
时,traceback
会自动链接。你可以通过 a) 忽略异常except: pass
或者直接操作sys.exc_info()
来去掉 traceback。
详细的回答
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
在2005年,PEP 3134,异常链和嵌入式追踪引入了异常链的概念:
- 隐式链,通过显式的
raise EXCEPTION
或隐式的抛出(__context__
属性); - 显式链,通过显式的
raise EXCEPTION from CAUSE
(__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
。compute()
函数捕获了这个异常并调用log(exc)
,但log()
函数在尝试写入一个未打开的文件时也会抛出异常。在现在的Python中,调用
compute()
的地方会抛出一个IOError
。而ZeroDivisionError
就会消失。通过提议的更改,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)
[…]
在2012年,PEP 415,引入异常属性的上下文抑制,通过显式的 raise EXCEPTION from None
(__suppress_context__
属性)。
提议
将在
BaseException
上引入一个新属性__suppress_context__
。每当__cause__
被设置时,__suppress_context__
将被设置为True
。特别是,raise exc from cause
语法将使exc.__suppress_context__
设置为True
。异常打印代码将检查该属性,以确定是否打印上下文和原因。__cause__
将恢复其原始目的和值。对于
__suppress_context__
,有print_line_and_file
异常属性的先例。总之,
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)
这里的区别在于,当你使用 from
时,__cause__
属性 会被设置,并且信息会说明这个异常是 直接由某个原因引起的。如果你不使用 from
,那么就不会设置 __cause__
,但可能会设置 __context__
属性,这时追踪信息会显示为 在处理过程中发生了其他事情。
设置 __context__
是在异常处理器中使用 raise
时发生的;如果你在其他地方使用 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
还可以查看 内置异常文档,了解附加到异常的上下文和原因信息的详细信息。