Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Catching exceptions based on their abstract base class

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.

like image 276
abingham Avatar asked May 27 '14 13:05

abingham


People also ask

Will a catch statement catch a derived exception if it is looking for the base class?

Yes, it will catch exceptions derived from the base.

What happens if we catch base class exception before child class exception?

C++ In Java, catching a base class exception before derived is not allowed by the compiler itself. In C++, the compiler might give a warning about it but compiles the code.

What does raise NotImplementedError () do?

NotImplemented is a special value which should be returned by the binary special methods to indicate that the operation is not implemented with respect to the other type. Raise NotImplementedError to indicate that a super-class method is not implemented and that child classes should implement it.

What will happen if an exception occurs in the base class in Java?

The Java specification requires that if an exception is thrown, it is either handled by a try/catch statement, or that the function is declared with "throws XYZException". This has the exception of RuntimeException, where it is OK if this is thrown without being caught.


1 Answers

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.

like image 139
ecatmur Avatar answered Sep 23 '22 12:09

ecatmur