Python中raise…from用法浅析

Python中raise…from用法浅析

本来这几天是计划阅读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 来禁止自动显示异常上下文。

虽然我暂时不清楚他实际的用处在哪,不过在知乎中我看到一位网友的说到,感觉以后肯定会用到的,就先mark吧。

参考文章:

https://zhuanlan.zhihu.com/p/52091476

https://blog.csdn.net/jpch89/article/details/84315444

zgao

如果有什么技术上的问题,可以加我的qq 1761321396 一起交流。