Björn Pollex
Björn Pollex

Reputation: 76866

How to create a class with an abstract `__init__` method?

I want to create an abstract base class in Python where part of the contract is how instances can be created. The different concrete implementations represent various algorithms that can be used interchangeably. Below is a simplified example (usual disclaimer - the real use-case is more complex):

from abc import ABC, abstractmethod
from typing import Type


class AbstractAlgorithm(ABC):
    @abstractmethod
    def __init__(self, param: int):
        pass

    @abstractmethod
    def get_result(self) -> int:
        pass


class ConcreteAlgorithm(AbstractAlgorithm):
    def __init__(self, param: int):
        self._param = param

    def get_result(self) -> int:
        return self._param * 2


def use_algorithm(algorithm: Type[AbstractAlgorithm]) -> int:
    a = algorithm(10)
    return a.get_result()

The above works, but has the drawback that I can't call super().__init__(...) in ConcreteAlgorithm.__init__, which might break certain inheritance scenarios, I think (correct me if I'm wrong here, but calling super is important for multiple inheritance, right?). (Strictly speaking __init__ can be called, but with the same signature as the subclass __init__, which doesn't make sense).

Python classes are callables, so I could also express it like this:

from abc import ABC, abstractmethod
from typing import Callable


class AbstractAlgorithm(ABC):
    @abstractmethod
    def get_result(self) -> int:
        pass


class ConcreteAlgorithm(AbstractAlgorithm):
    def __init__(self, param: int):
        self._param = param

    def get_result(self) -> int:
        return self._param * 2


def use_algorithm(algorithm: Callable[[int], AbstractAlgorithm]) -> int:
    a = algorithm(10)
    return a.get_result()


print(use_algorithm(ConcreteAlgorithm))

This works and doesn't have the drawback mentioned above, but I do like having the __init__-signature in the abstract base class for documentation purposes.

Finally, it is possible to have abstract classmethods, so this approach works as well:

from abc import ABC, abstractmethod
from typing import Type


class AbstractAlgorithm(ABC):
    @classmethod
    @abstractmethod
    def initialize(cls, param: int) -> "AbstractAlgorithm":
        pass

    @abstractmethod
    def get_result(self) -> int:
        pass


class ConcreteAlgorithm(AbstractAlgorithm):
    @classmethod
    def initialize(cls, param: int) -> "ConcreteAlgorithm":
        return cls(param)

    def __init__(self, param: int):
        self._param = param

    def get_result(self) -> int:
        return self._param * 2


def use_algorithm(algorithm: Type[AbstractAlgorithm]) -> int:
    a = algorithm.initialize(10)
    return a.get_result()


print(use_algorithm(ConcreteAlgorithm))

This works, but I lose the nice property of using algorithm like a callable (it's just more flexible, in case someone actually wants to drop in a function, for example to decide which algorithm to use based on certain parameter values).

So, is there an approach that satisfies all three requirements:

  1. Full documentation of the interface in the abstract base class.
  2. Concrete implementations usable as callables.
  3. No unsafe behavior like not being able to call the base-class __init__.

Upvotes: 1

Views: 3261

Answers (1)

deceze
deceze

Reputation: 522523

Strictly speaking __init__ can be called, but with the same signature as the subclass __init__, which doesn't make sense.

No, it makes perfect sense.

You're prescribing the signature because you require each child class to implement it exactly. That means you need to call it exactly like that as well. Each child class needs to call its super().__init__ exactly according to the abstract definition, passing all defined parameters along.

Upvotes: 2

Related Questions