timml icon indicating copy to clipboard operation
timml copied to clipboard

Implement Base classes as AbstractBaseClasses

Open Hugovdberg opened this issue 5 years ago • 2 comments

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 by Aquifer. However, the Aquifer 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. For Element.potinf it doesn't make sense to provide a default implementation, so the abstract version can simply contain pass.

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

Hugovdberg avatar Nov 08 '19 19:11 Hugovdberg

This is a good idea that has been on my wishlist for a while

mbakker7 avatar Nov 12 '19 17:11 mbakker7

I have already started doing this on a local branch, I will make a draft pull request so you can track my progress

Hugovdberg avatar Nov 14 '19 09:11 Hugovdberg