为什么引发异常会调用 __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
会被查看。
考虑以下使用 __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
callsPyObject_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
会被查看。