Catching exceptions based on their abstract base class

abstract-base-class, exception, python

Solution

The documentation says:

An object is compatible with an exception if it is the class or a base class of the exception object or a tuple containing an item compatible with the exception.

Unfortunately, this doesn't say whether virtual base classes should be considered, unlike the language for e.g. issubclass:

Return true if class is a subclass (direct, indirect or virtual) of classinfo. [...]

The language on overriding instance and subclass checks doesn't help much either:

The following methods are used to override the default behavior of the `isinstance()` and `issubclass()` built-in functions. [...]

In fact, as you have suspected, the CPython implementation (for Python 3) bypasses subclass checks, calling `PyType_IsSubtype` directly:

http://hg.python.org/cpython/file/3.4/Python/errors.c#l167

PyErr_GivenExceptionMatches(PyObject *err, PyObject *exc)
{
    ...
        /* PyObject_IsSubclass() can recurse and therefore is
           not safe (see test_bad_getattr in test.pickletester). */
        res = PyType_IsSubtype((PyTypeObject *)err, (PyTypeObject *)exc);

For reference, the CPython implementation of issubclass, PyObject_IsSubclass, calls `__subclasscheck__` before falling back to `PyType_IsSubtype`.

So there is a good reason for this behavior; exception handling needs to be non-recursive, so it isn't safe for it to call back up into Python code. Note that the Python 2.7 version accepts the risk of overflow and does call `PyObject_IsSubclass`. There is a proposal to relax this restriction in Python 3, but although a patch has been written it hasn't yet been accepted. Otherwise, it would be a good idea for the documentation to clarify that `except` checks are non-virtual.

Problem

Suppose I've got an exception class with an abstract base class, something like this: ``` class MyExceptions(BaseExeption, metaclass=abc.ABCMeta): pass class ProperSubclass(MyExceptions): pass MyExceptions.register(ValueError) ``` It appears that I can catch `ProperSubclass` by `MyExceptions`, but not `ValueError`: ``` try: raise ProperSubclass() except MyExceptions: print('As expected, control comes here...') except: print('...and not here.') try: raise ValueError() except MyExceptions: print('Control does not come here...') except ValueError: print('...but unexpectedly comes here.') ``` So my question is, should I be able to catch built-in exceptions by their abstract base class? If so, how? And if not, what are the rules? I guess another way of asking this is: do except clauses properly use isinstance()/issubclass() for matching, and if not (as appears to be the case) what do they use? Perhaps there are some shady shortcuts down in the C implementation.

Original source