1

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.

LSchueler
  • 1,414
  • 12
  • 23
  • Can you show your code? – C.Nivs Aug 23 '18 at 19:06
  • 1
    Instead of abstract base classes, try adding an `__init_subclass__` method which performs some sort of `hasattr(subclass, 'method')` and raise an error if none of the 4 checks return true – N Chauhan Aug 23 '18 at 19:13
  • Have a look at the way [`functools.total_ordering`](https://github.com/python/cpython/blob/master/Lib/functools.py#L81-L197) handles something like this. It's not pretty, and basically python does a bunch of dynamic checks. -- I think you need to rethink your design. In any static language this is impossible and for good reason -- your API should be simple and clear. – FHTMitchell Aug 24 '18 at 09:09
  • @NChauhan: As currently written, it cannot be more than a comment. But if you elaborate it, it would would probably be the best answer... – Serge Ballesta Aug 24 '18 at 09:19
  • @FHTMitchell Thank you for your comment and for pointing out functools.total_ordering, that should work. The class was designed in order to let the user very easily create subclasses as simple as possible. With good documentation and a good example, the user will never have to look into the base class. – LSchueler Aug 24 '18 at 09:37

1 Answers1

2

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.

LSchueler
  • 1,414
  • 12
  • 23
  • 1
    Couldn't you just do `if not all(hasattr(cls, method) for method in ('a', 'b', 'c', 'd')): raise TypeError` instead? – N Chauhan Aug 24 '18 at 17:35
  • 1
    @NChauhan: i think, you mean "any" .. and if you do so, you still haven't assigned the missing methods. MosteM: What do you need "ABCMeta" for in your case? – MuellerSeb Aug 27 '18 at 15:16
  • @greeeeeeen Just to make clear what my intentions are, I know that it does not have any direct consequence. – LSchueler Aug 28 '18 at 06:08