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

Why does raising an exception invoke __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__not 被调用。

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

Under certain circumstances, the values returned by PyErr_Fetch() below can be “unnormalized”, meaning that *exc is a class object but *val is not an instance of the same class.

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

您的情况如下:

  • 最终在处理异常时,PyErr_Print is called, where it calls _PyErr_NormalizeException
  • _PyErr_NormaliizeException calls PyObject_IsSubclass.
  • 如果提供
  • PyObject_IsSubclass 使用 __subclasscheck__

我不能说“*exc 是一个 class 对象但 *val 不是同一 class 的实例”的那些“特定情况”是什么(可能需要向后兼容 - 我不知道)。


我的第一个假设是,当 CPython 确保(即 here)异常来自 BaseException.

时,它就会发生。

代码如下

class OldStyle():
    pass

raise OldStyle

会为 Python2 提高 OldStyle,但

会提高 TypeError: exceptions must be old-style classes or derived from BaseException, not type
class NewStyle(object):
    pass

raise NewStyle

或 Python3 中的 TypeError: exceptions must derive from BaseException 因为在 Python3 中所有 class 都是“新样式”。

但是,对于此检查,使​​用的不是 PyObject_IsSubclass,而是 PyType_FastSubclass

#define PyExceptionClass_Check(x)                                       \
    (PyType_Check((x)) &&                                               \
     PyType_FastSubclass((PyTypeObject*)(x), Py_TPFLAGS_BASE_EXC_SUBCLASS))

即只有 tpflag 会被查看。