I'm trying to create a custom base class for exceptions raised within my application. I want to make it an abstract class (using the abc module) that cannot be directly instantiated, to force myself to define more specific concrete subclasses for different types of error situations.
I can define an abstract class that inherits from a custom concrete class. However, to my surprise, if I make the abstract class inherit (directly or indirectly) from Exception, it can again be instantiated directly, defeating the purpose of making it abstract in the first place.
I want to understand what's happening here so that I can make my custom exception class abstract for real and not directly instantiable.
I've tried different variations of declaring my custom exception class as abstract, including using the metaclass=abc.ABCMeta
syntax as well as inheriting from abc.ABC
. Regardless of the way it is declared as abstract, it ceases to behave like an abstract class as soon as I make it inherit from Exception
.
Relevant Python versions for me are 3.5, 3.6 and 3.7. I've tested the below code on Python 3.5.2 (Ubuntu 16.04) and 3.6.8 (Ubuntu 18.04).
The following seems to work as expected: instantiating AppException fails because of the abstract method (TypeError: Can't instantiate abstract class AppException with abstract methods abs_method
).
Note that although the classes are called *Exception
, they are not (yet) inheriting from the real built-in Exception class.
import abc
class BaseException():
pass
class AppException(BaseException, abc.ABC):
@abc.abstractmethod
def abs_method(self):
pass
class ConcreteException(AppException):
def abs_method(self):
return "concrete method"
# BaseException can be instantiated just fine
a = BaseException()
# ConcreteException can be instantiated just fine
c = ConcreteException()
# It shouldn't be possible to instantiate AppException directly,
# so this line should raise a TypeError
b = AppException()
When I change the definition of BaseException to inherit from the actual Exception
class:
class BaseException(Exception):
pass
then the TypeError
goes away, so the instantiation of AppException did work this time. AppException
is no longer behaving as an abstract class, even though in my understanding, it should.
Here is the actual code, currently stuck as a draft PR until I can figure out what's going on.