Can one declare an abstract exception in Python?

Question:

I would like to declare a hierarchy of user-defined exceptions in Python. However, I would like my top-level user-defined class (TransactionException) to be abstract. That is, I intend TransactionException to specify methods that its subclasses are required to define. However, TransactionException should never be instantiated or raised.

I have the following code:

from abc import ABCMeta, abstractmethod

class TransactionException(Exception):
    __metaclass__ = ABCMeta

    @abstractmethod
    def displayErrorMessage(self):
        pass

However, the above code allows me to instantiate TransactionException

a = TransactionException()

In this case a is meaningless, and should instead draw an exception. The following code removes the fact that TransactionException is a subclass of Exception

from abc import ABCMeta, abstractmethod

class TransactionException():
    __metaclass__ = ABCMeta

    @abstractmethod
    def displayErrorMessage(self):
        pass

This code properly prohibits instantiation but now I cannot raise a subclass of TransactionException because it’s not an Exception any longer.

Can one define an abstract exception in Python? If so, how? If not, why not?

NOTE: I’m using Python 2.7, but will happily accept an answer for Python 2.x or Python 3.x.

Asked By: Matthew Ross

||

Answers:

class TransactionException(Exception):

    def __init__(self, *args, **kwargs):
        raise NotImplementedError('you should not be raising this')


class EverythingLostException(TransactionException):

    def __init__(self, msg):
        super(TransactionException, self).__init__(msg)


try:
    raise EverythingLostException('we are doomed!')
except TransactionException:
    print 'check'

try:
    raise TransactionException('we are doomed!')
except TransactionException:
    print 'oops'
Answered By: Vasiliy Faronov

There’s a great answer on this topic by Alex Martelli here. In essence, it comes down how the object initializers (__init__) of the various base classes (object, list, and, I presume, Exception) behave when abstract methods are present.

When an abstract class inherits from object (which is the default, if no other base class is given), its __init__ method is set to that of object‘s, which performs the heavy-lifting in checking if all abstract methods have been implemented.

If the abstract class inherits from a different base class, it will get that class’ __init__ method. Other classes, such as list and Exception, it seems, do not check for abstract method implementation, which is why instantiating them is allowed.

The other answer provides a suggested workaround for this. Of course, another option that you have is simply to accept that the abstract class will be instantiable, and try to discourage it.

Answered By: voithos

My implementation for an abstract exception class, in which the children of the class work out of the box.


class TransactionException(Exception):

    def __init__(self):
        self._check_abstract_initialization(self)

    @staticmethod
    def _check_abstract_initialization(self):
        if type(self) == TransactionException:
            raise NotImplementedError("TransactionException should not be instantiated directly")


class AnotherException(TransactionException):
    pass


TransactionException() # NotImplementedError: TransactionException should not be instantiated directly

AnotherException # passes
Answered By: Alonme

Here’s a helper function that can be used in such scenario:

def validate_abstract_methods(obj):
    abstract_methods = []
    for name in dir(obj):
        value = getattr(obj, name, None)
        if value is not None and getattr(value, '__isabstractmethod__', False):
            abstract_methods.append(name)
    if abstract_methods:
        abstract_methods.sort()
        raise TypeError(f"Can't instantiate abstract class {obj.__class__.__name__} with abstract methods {', '.join(abstract_methods)}")

This function roughly does the same thing as abc.ABC class – you just need to call it from your class’ __init__ method.

Answered By: Maciej Wiatrzyk
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.