为什么引发异常会调用__subclasscheck__?

问题描述

考虑以下示例,该示例使用__subclasscheck__作为自定义异常类型:

class MyMeta(type):
    def __subclasscheck__(self,subclass):
        print(f'__subclasscheck__({self!r},{subclass!r})')

class MyError(Exception,Metaclass=MyMeta):
    pass

现在,当引发此类异常时,将调用__subclasscheck__方法;即raise MyError()的结果是:

__subclasscheck__(<class '__main__.MyError'>,<class '__main__.MyError'>)
Traceback (most recent call last):
  File "test.py",line 8,in <module>
    raise MyError()
__main__.MyError

在这里输出的第一行显示__subclasscheck__调用来检查MyError是否是其本身的子类,即issubclass(MyError,MyError)。我想了解为什么这是必需的,以及它通常如何有用。


我正在使用cpython 3.8.1重现此行为。我还尝试了PyPy3(3.6.9),这里没有调用__subclasscheck__

解决方法

我想这是CPython实现的细节。如PyErr_NormalizeException的文档所述:

在某些情况下,PyErr_Fetch()返回的值 下面可以是“未规范化”,这意味着*exc是一个类对象,但是 *val不是同一类的实例。

因此,在处理引发的错误的某个时候,CPython会将异常规范化,因为否则它无法假定错误的值是正确的类型。

在您的情况下,情况如下:

  • 最终,在处理异常时,会PyErr_Print _PyErr_NormalizeException被调用calls
  • _PyErr_NormaliizeException calls PyObject_IsSubclass
  • PyObject_IsSubclass使用__subclasscheck__(如果提供)。

我不能说“ *exc的那些“某些情况”是一个类对象,但是*val不是同一类的实例”(为了向后兼容,可能需要-我不知道知道)。


我的第一个假设是,当CPython确保(即here)该异常源自BaseException时发生。

以下代码

class OldStyle():
    pass

raise OldStyle

对于Python2,将会提高OldStyle,对于{p> 1,则会{1>}

TypeError: exceptions must be old-style classes or derived from BaseException,not type

class NewStyle(object): pass raise NewStyle 在Python3中,因为在Python3中所有类都是“新样式”。

但是,此检查不使用TypeError: exceptions must derive from BaseException而是使用PyType_FastSubclass

PyObject_IsSubclass

即仅查看#define PyExceptionClass_Check(x) \ (PyType_Check((x)) && \ PyType_FastSubclass((PyTypeObject*)(x),Py_TPFLAGS_BASE_EXC_SUBCLASS))