Search code examples
pythonabstract-classabc

Python ABC classes: One of multiple methods has to be overridden


In Python, I have an abstract base class which has four methods, of which at least one has to be overwritten. Is it possible to somehow implement this with the @abstractmethod decorator or something similar?

Here is a stripped down version of the base class:

from abc import ABCMeta

class Base(metaclass=ABCMeta):
    def __init__(self, var):
        self.var = var

    def a(self, r):
        return self.var - self.b(r)

    def b(self, r):
        return self.var * self.c(r)

    def c(self, r):
        return 1. - self.d(r)

    def d(self, r):
        return self.a(r) / self.var

The four methods have some kind of cyclic dependency and a subclass has to override at least one of these methods. The rest of the methods then work from the base class.

It might seem a bit strange, but it makes perfectly sense in the application I'm working on.


Solution

  • Thanks to the hint from @NChauhan, I came up with following solution and thanks to a second hint from @NChauhan and from @greeeeeeen, the solution gets a bit shorter and more readable:

    from abc import ABCMeta
    
    
    class Base(metaclass=ABCMeta):
        def __init__(self, var):
            self.var = var
    
        def __init_subclass__(cls):
            super().__init_subclass__()
    
            def a(self, r):
                return self.var - self.b(r)
    
            def b(self, r):
                return self.var * self.c(r)
    
            def c(self, r):
                return 1. - self.d(r)
    
            def d(self, r):
                return self.a(r) / self.var
    
            if not hasattr(cls, 'a'):
                cls.a = a
            if not hasattr(cls, 'b'):
                cls.b = b
            if not hasattr(cls, 'c'):
                cls.c = c
            if not hasattr(cls, 'd'):
                cls.d = d
    
            if not any(hasattr(cls, method) for method in ('a', 'b', 'c', 'd')):
                raise TypeError(f"Can't instantiate class '{cls.__name__}', " +
                                "without overriding at least on of the methods " +
                                "'a', 'b', 'c', or 'd'.")
    

    If I would not assign the four methods in the __init_subclass__ method, hasattr would return True, because the methods would get inherited.