本来这几天是计划阅读string模块的源码,恰好其中一段异常处理的代码我觉得很新奇,也就是raise…from的用法,raise的用法大家都知道。因为我之前没遇到过,所以就去网上查了相关的资料,自己试验一下。
我阅读的这段代码取自于string模块的Formatter类的format方法,源码如下:
class Formatter:
def format(*args, **kwargs):
if not args:
raise TypeError("descriptor 'format' of 'Formatter' object "
"needs an argument")
self, *args = args
try:
format_string, *args = args
except ValueError:
if 'format_string' in kwargs:
format_string = kwargs.pop('format_string')
import warnings
warnings.warn("Passing 'format_string' as keyword argument is "
"deprecated", DeprecationWarning, stacklevel=2)
else:
raise TypeError("format() missing 1 required positional "
"argument: 'format_string'") from None
return self.vformat(format_string, args, kwargs)
在异常捕获的else分支中,用到了raise…from的语法。而且这里比较特别的是raise…from None。我之前一直不知道这样的用法,所以就自己试验了一下。
例如我们直接用两段代码来对比一下。
try:
raise IndexError
except Exception as e:
raise ValueError
这里就是我们常见的异常捕获的写法,不过在下面的捕获中又引发了一个新的异常,报错信息如下。
Traceback (most recent call last):
File “E:/pythonlab/test127.py”, line 2, in <module>
raise IndexError
IndexError
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File “E:/pythonlab/test127.py”, line 4, in <module>
raise ValueError
ValueError
try:
raise IndexError
except Exception as e:
raise ValueError from e
再用raise…from的语法试试。
Traceback (most recent call last):
File “E:/pythonlab/test127.py”, line 2, in <module>
raise IndexError
IndexError
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File “E:/pythonlab/test127.py”, line 4, in <module>
raise ValueError from e
ValueError
对比发现其实差别在于提示信息的不同
- During handling of the above exception, another exception occurred:
- The above exception was the direct cause of the following exception:
前一个是 在处理上述异常期间,发生了另一个异常。
后者为 上述异常是以下异常的直接原因。
try:
raise IndexError
except Exception as e:
raise ValueError from None
而如果将其改为from None,那么报错如下。
Traceback (most recent call last):
File “E:/pythonlab/test127.py”, line 4, in <module>
raise ValueError from None
ValueError
此时报错提示信息也简洁了很多。
现在再分析一下其原理。
当在 except 块或者 finally 块中出现异常时(包括使用单独的 raise 重新抛出异常的情况),之前的异常会被附加到新异常的 __context__ 属性上。
而在其他任何地方抛出异常,都不会设置 __context__ 属性。
这样打印出来的异常信息就会包含这么一句话:During handling of the above exception, another exception occurred:。
raise A from B
raise A from B 语句用于连锁 chain 异常。
from 后面的 B 可以是:
- 异常类
- 异常实例
- None(Python 3.3 的新特性)
如果 B 是异常类或者异常实例,那么 B 会被设置为 A 的 __cause__ 属性,表明 A异常 是由 B异常 导致的。
这样打印出来的异常信息就会包含这样一句话:The above exception was the direct cause of the following exception:。
与此同时,在 Python 3.3 中 A异常 的 __suppress_context__ 属性会被设置为 True,这样就抑制了 A异常 的 __context__ 属性,即忽略 __context__ 属性。
于是 Python 就不会自动打印异常上下文 exception context,而是使用 __cause__ 属性来打印异常的引发者。
在 Python 3.3 中,B 还可以是 None:raise A异常 from None。
这样相当于把 __suppress_context__ 属性设置为 True,从而禁用了 __context__ 属性,Python 不会自动展示异常上下文。
下面是Python中所有异常的基类BaseException类的代码。
class BaseException(object):
""" Common base class for all exceptions """
def with_traceback(self, tb): # real signature unknown; restored from __doc__
"""
Exception.with_traceback(tb) --
set self.__traceback__ to tb and return self.
"""
pass
def __delattr__(self, *args, **kwargs): # real signature unknown
""" Implement delattr(self, name). """
pass
def __getattribute__(self, *args, **kwargs): # real signature unknown
""" Return getattr(self, name). """
pass
def __init__(self, *args, **kwargs): # real signature unknown
pass
@staticmethod # known case of __new__
def __new__(*args, **kwargs): # real signature unknown
""" Create and return a new object. See help(type) for accurate signature. """
pass
def __reduce__(self, *args, **kwargs): # real signature unknown
pass
def __repr__(self, *args, **kwargs): # real signature unknown
""" Return repr(self). """
pass
def __setattr__(self, *args, **kwargs): # real signature unknown
""" Implement setattr(self, name, value). """
pass
def __setstate__(self, *args, **kwargs): # real signature unknown
pass
def __str__(self, *args, **kwargs): # real signature unknown
""" Return str(self). """
pass
args = property(lambda self: object(), lambda self, v: None, lambda self: None) # default
__cause__ = property(lambda self: object(), lambda self, v: None, lambda self: None) # default
"""exception cause"""
__context__ = property(lambda self: object(), lambda self, v: None, lambda self: None) # default
"""exception context"""
__suppress_context__ = property(lambda self: object(), lambda self, v: None, lambda self: None) # default
__traceback__ = property(lambda self: object(), lambda self, v: None, lambda self: None) # default
__dict__ = None # (!) real value is ''
这样就可以理解得更加深刻些了。引用网上的总结。
在异常处理程序或 finally
块中引发异常,Python 会为异常设置上下文,可以手动通过 with_traceback()
设置其上下文,或者通过 from
来指定异常因谁引起的。这些手段都是为了得到更友好的异常回溯信息,打印清晰的异常上下文。若要忽略上下文,则可以通过 raise ... from None
来禁止自动显示异常上下文。