Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Base classes as AbstractBaseClasses #31

Open
Hugovdberg opened this issue Nov 8, 2019 · 2 comments
Open

Implement Base classes as AbstractBaseClasses #31

Hugovdberg opened this issue Nov 8, 2019 · 2 comments

Comments

@Hugovdberg
Copy link
Contributor

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
@mbakker7
Copy link
Owner

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

@Hugovdberg
Copy link
Contributor Author

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants