Why do virtual subclasses of an abstract Exception
created using the ABCMeta.register
not match under the except
clause?
I'd like to ensure that exceptions that get thrown by a package that I'm using are converted to MyException
, so that code which imports my module can catch any exception my module throws using except MyException:
instead of except Exception
so that they don't have to depend on an implementation detail (the fact that I'm using a third-party package).
To do this, I've tried registering an OtherException
as MyException
using an abstract base class:
# Tested with python-3.6
from abc import ABC
class MyException(Exception, ABC):
pass
class OtherException(Exception):
"""Other exception I can't change"""
pass
MyException.register(OtherException)
assert issubclass(OtherException, MyException) # passes
try:
raise OtherException("Some OtherException")
except MyException:
print("Caught MyException")
except Exception as e:
print("Caught Exception: {}".format(e))
The assertion passes (as expected), but the exception falls to the second block:
Caught Exception: Some OtherException
Alright, I looked into this some more. The answer is that it's a long-outstanding open issue in Python3 (there since the very first release) and apparently was first reported in 2011. As Guido said in the comments, "I agree it's a bug and should be fixed." Unfortunately, this bug has lingered due to concerns about the performance of the fix and some corner cases that need to be handled.
The core issue is that the exception matching routine PyErr_GivenExceptionMatches
in errors.c
uses PyType_IsSubtype
and not PyObject_IsSubclass
. Since types and objects are supposed to be the same in python3, this amounts to a bug.
I've made a PR to python3 that seems to cover all of the issues discussed in the thread, but given the history I'm not super optimistic it's going to get merged soon. We'll see.