Python“raise from”用法
- 2025-03-18 08:54:00
- admin 原创
- 49
问题描述:
raise
Python 中的和有什么区别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
解决方案 1:
不同之处在于,当您使用 时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
另请参阅内置异常文档,了解有关异常的上下文和原因信息的详细信息。
解决方案 2:
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__)
The above exception was the direct cause...'
elif exc.__context__: print_chain(exc.__context__) print '
During handling of the above exception, ...'
print_exc(exc)
2012 年,PEP 415《使用异常属性实现上下文抑制》raise EXCEPTION from None
引入了使用显式(属性)实现异常上下文抑制的功能__suppress_context__
。
提议
将引入
BaseException
、 、的新属性。每当设置 时,将设置为。特别是,语法将设置为。异常打印代码将检查该属性以确定是否打印上下文和原因。将返回其原始目的和值。__suppress_context__
`__cause____suppress_context__
Trueraise 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 '
The above exception was the direct cause...'
elif exc.__context__ and not exc.__suppress_context__:
print_chain(exc.__context__)
print '
During handling of the above exception, ...'
print_exc(exc)
解决方案 3:
最简短的答案。PEP -3134说明了一切。raise Exception from e
设置__cause__
新异常的文件。
来自同一 PEP 的较长答案:
__context__
字段将被隐式设置为块内的原始错误,except:
除非被告知不要这样做__suppress_context__ = True
。__cause__
就像上下文一样,但必须使用from
语法明确设置traceback
`raise在块内调用时将始终链接
except。您可以通过以下方式摆脱回溯: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"
EXECURTING FUNCTION '{func.__name__}'
")
try:
func()
except Exception as e:
"Here is result of our actions:"
print(f" Exception type: '{type(e)}'")
print(f" Exception message: '{e}'")
print(f" Exception context: '{e.__context__}'")
print(f" Context type: '{type(e.__context__)}'")
print(f" Exception cause: '{e.__cause__}'")
print(f" Cause type: '{type(e.__cause__)}'")
print("
TRACEBACKSTART>>>")
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
解决方案 4:
前言
文档中对此进行了技术解释。但它非常令人困惑,而且缺乏示例。以下是一些示例,我认为它们比__context__
、__cause__
、有趣的事情更清楚地解释了这种情况__supress_context__
。
主要例子
import sys
from traceback import print_exc
class ExceptionA(Exception): pass
class ExceptionB(Exception): pass
print('## Naked exception ##')
try:
raise ExceptionA
except:
print_exc(file=sys.stdout)
print('
## except and raise ##')
try:
try:
raise ExceptionA
except ExceptionA:
raise ExceptionB
except:
print_exc(file=sys.stdout)
print('
## except and raise from e ##')
try:
try:
raise ExceptionA
except ExceptionA as e:
raise ExceptionB from e
except:
print_exc(file=sys.stdout)
print('
## except and raise from None ##')
try:
try:
raise ExceptionA
except ExceptionA:
raise ExceptionB from None
except:
print_exc(file=sys.stdout)
产量
## Naked exception ##
Traceback (most recent call last):
File "...
aise.py", line 9, in <module>
raise ExceptionA
ExceptionA
## except and raise ##
Traceback (most recent call last):
File "...
aise.py", line 16, in <module>
raise ExceptionA
ExceptionA
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "...
aise.py", line 18, in <module>
raise ExceptionB
ExceptionB
## except and raise from e ##
Traceback (most recent call last):
File "...
aise.py", line 25, in <module>
raise ExceptionA
ExceptionA
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "...
aise.py", line 27, in <module>
raise ExceptionB from e
ExceptionB
## except and raise from None ##
Traceback (most recent call last):
File "...
aise.py", line 36, in <module>
raise ExceptionB from None
ExceptionB
因此我们得到了 4 种行为。
除了“处理”之外,我们仅打印出
ExceptionA
。当在处理异常 A 期间直接引发新的异常 B 时,我们会看到 A 和 B 的回溯,并由以下消息连接起来:“在处理上述异常期间,发生了另一个异常:”
当在处理 A 的过程中从 A 引发新的异常 B 时,我们会看到 A 和 B 的回溯,并连接着以下消息“上述异常是导致以下异常的直接原因:”
当在处理 A 的过程中从 None 引发新的异常 B 时,我们只能看到 B 的回溯。
为什么raise from e
?
我很难看出选项 2 和 3 之间的实际区别。唯一的区别是串联回溯之间的消息,因此区别纯粹与人类读者有关。因此两者具有不同的语义。据我所知,第二个选项 ( raise ExceptionB
) 的语义表示:我们正在处理异常 A,然后发生了另一个意外异常 B。第三个选项 ( raise ExceptionB from e
) 表示我们正在从内部异常转变ExceptionB
为可能更具信息性的面向用户的异常。我认为一个常见的用例是这样的
from traceback import print_exc
import sys
my_dict = {'a': 1}
print('## No handling ##')
try:
print(my_dict['b'])
except:
print_exc(file=sys.stdout)
print('
## Handling the exception to add more info for the reader ##')
try:
try:
print(my_dict['b'])
except KeyError as e:
raise KeyError('You forgot to include a key in your dict!') from e
except:
print_exc(file=sys.stdout)
产量
## No handling ##
Traceback (most recent call last):
File "...
aise_2.py", line 9, in <module>
print(my_dict['b'])
KeyError: 'b'
## Handling the exception to add more info for the reader ##
Traceback (most recent call last):
File "...
aise_2.py", line 16, in <module>
print(my_dict['b'])
KeyError: 'b'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "...
aise_2.py", line 18, in <module>
raise KeyError('You forgot to include a key in your dict!') from e
KeyError: 'You forgot to include a key in your dict!'
这里的重点是,我们不希望回溯仅仅指出第二个键错误“发生在处理第一个键错误期间”。更有帮助的是指出第二个键错误发生是因为第一个键错误发生。也就是说,它们有相同的原因(因此__cause__
)。因此,从这个意义上讲,开发人员可以向阅读回溯的人传达更多信息。但是,在某些情况下,您可能希望完全隐藏根本原因,并为用户提供更短、更具描述性的回溯。在这种情况下,您可以使用raise ... from None
。
扫码咨询,免费领取项目管理大礼包!