Consider the following example that uses __subclasscheck__
for a custom exception type:
class MyMeta(type):
def __subclasscheck__(self, subclass):
print(f'__subclasscheck__({self!r}, {subclass!r})')
class MyError(Exception, metaclass=MyMeta):
pass
Now when raising an exception of this type, the __subclasscheck__
method gets invoked; ie raise MyError()
results in:
__subclasscheck__(<class '__main__.MyError'>, <class '__main__.MyError'>)
Traceback (most recent call last):
File "test.py", line 8, in <module>
raise MyError()
__main__.MyError
Here the first line of the output shows that __subclasscheck__
got invoked to check whether MyError
is a subclass of itself, ie issubclass(MyError, MyError)
. I'd like to understand why that's necessary and how it's useful in general.
I'm using CPython 3.8.1 to reproduce this behavior. I also tried PyPy3 (3.6.9) and here __subclasscheck__
is not invoked.
I guess this is a CPython implementation detail. As stated in documentation to 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.
So sometime during the processing of the raised error, CPython will normalize the exception, because otherwise it cannot assume that the value of the error is of the right type.
In your case it happens as follows:
PyErr_Print
is called, where it calls _PyErr_NormalizeException
._PyErr_NormaliizeException
calls PyObject_IsSubclass
. PyObject_IsSubclass
uses __subclasscheck__
if it is provided. I cannot say what those "certain circumstances" for " *exc
is a class object but *val
is not an instance of the same class" are (maybe needed for backward compatibility - I don't know).
My first assumption was, that it happens, when CPython ensures (ie here ), that the exception is derived from BaseException
.
The following code
class OldStyle():
pass
raise OldStyle
would raise OldStyle
for Python2, but TypeError: exceptions must be old-style classes or derived from BaseException, not type
for
class NewStyle(object):
pass
raise NewStyle
or TypeError: exceptions must derive from BaseException
in Python3 because in Python3 all classes are "new style".
However, for this check not PyObject_IsSubclass
but PyType_FastSubclass
is used:
#define PyExceptionClass_Check(x) \
(PyType_Check((x)) && \
PyType_FastSubclass((PyTypeObject*)(x), Py_TPFLAGS_BASE_EXC_SUBCLASS))
ie only the tpflag
s are looked at.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.