timml
timml copied to clipboard
Implement Base classes as AbstractBaseClasses
Python supports abstract base classes (ABCs) through the abc
module in stdlib. This allows you to mark methods that should be overloaded by child classes as such by using the @abstractmethod
decorator.
The advantages of this are the following:
- Trying to directly create an object from an ABC raises a
TypeError
that the class is not meant to be instantiated directly. - Trying to create an object from a class that inherits from an ABC, but does not implement all abstract methods raises the same error.
- The previously mentioned errors mean that code will fail soon and fail hard, and not only when somewhere down the line a child class is used that doesn't implement all methods.
- Abstract methods can still have an implementation to return some default value as a fallback. In the example below of the
AquiferData.isinside
method is marked as abstract, and should therefore be explicitly overloaded byAquifer
. However, theAquifer
class can explicitly subscribe to the default implementation to return True. For the inhomogeneities the default behaviour isn't appropriate and can be implemented as necessary. ForElement.potinf
it doesn't make sense to provide a default implementation, so the abstract version can simply containpass
.
An example where this could be used is AquiferData
. By changing this:
class AquiferData:
[...]
def isinside(self, x, y):
raise Exception("Must overload AquiferData.isinside()")
to
from abc import ABC
class AquiferData(ABC):
[...]
@abstractmethod
def isinside(self, x, y):
return True
[...]
class Aquifer(AquiferData):
[...]
def isinside(self, x, y):
return super().isinside(x, y)
you get the same principle, but stricter enforcement.
As an example how these classes work:
from abc import ABC, abstractmethod
class Base(ABC):
def __init__(self, a):
self.a = a
@abstractmethod
def add(self, b):
pass
@abstractmethod
def subtract(self, b):
pass
class Child(Base):
def add(self, b):
return self.a + b
class GrandChild(Child):
def subtract(self, b):
return self.a - b
try:
a = Base(1)
except Exception as e:
print(type(e).__name__, ":", e)
try:
b = Child(1)
except Exception as e:
print(type(e).__name__, ":", e)
try:
c = GrandChild(1)
print("Adding 2:", c.add(2))
print("Subtracting 1:", c.subtract(1))
except Exception as e:
print(e)
produces this output:
TypeError : Can't instantiate abstract class Base with abstract methods add, subtract
TypeError : Can't instantiate abstract class Child with abstract methods subtract
Adding 2: 3
Subtracting 1: 0
This is a good idea that has been on my wishlist for a while
I have already started doing this on a local branch, I will make a draft pull request so you can track my progress