7

Is there a way to mark a python class as abstract or un-instantiable even if all its abstract methods have been implemented?

class Component(ABC):
    @abstractmethod
    def operation(self) -> None:
        pass

class Decorator(Component): # I would like this class to be abstract
    def operation(self) -> None:
        print("basic operation")

The only workaround I found is to choose some method in the class to have both implementation and @abstractmethod decorator, but then python requires derivers of the child abstract class to re-implement the method.

This too can be worked around by having the child class calling super() , but pylint complains that this is a useless call.

class Component(ABC):
    @abstractmethod
    def operation(self) -> None:
        pass

class Decorator(Component): # I would like this class to be abstract
    @abstractmethod
    def operation(self) -> None:
        print("basic operation")

class ConcreteDecorator(Decorator): 
    def operation(self) -> None: # python makes child class re-implement
        super().operation()  # pylint complains about useless super delegation

Is there a better way to do this?
I tried using a method with implementation and @abstractmethod decorator, but then deriving classes need to reimplement. I'm looking for a solution at "compile time", not a run time error.

Gonen I
  • 5,576
  • 1
  • 29
  • 60
  • 1
    If all of the methods are implemented, why _is_ it considered uninstantiable? Can you provide some context on the actual problem you're trying to solve? – jonrsharpe Oct 19 '21 at 16:19
  • 2
    Sure, for example the BaseDecorator in the GOF decorator pattern. It forwards all calls as a base for Concrete Decorators that derive from it, but it would be a mistake to use it on its own. Doing this is possible in Java/C++/C# – Gonen I Oct 19 '21 at 16:23
  • 2
    Sounds like a `pylint` issue — can't you silence it someway?. BTW, `@abstractmethod` is a decorator, not an annotation (which is something entirely different). – martineau Oct 19 '21 at 16:31
  • 4
    I agree with this being `pylint`'s problem, not yours. But from another perspective, an abstract class is less about *preventing* instantiation than it is about ensuring that instantiation is *safe*. If you don't want `Decorator` instantiated, do what you would for any other class you don't want others to use: name it `_Decorator`, document it as a private implemantion detail, and require others to create subclasses. If `Decorator.operation` is sufficient, then leave it up to the subclasser whether they want to do more than simply call it by overriding it. – chepner Oct 19 '21 at 16:52
  • 2
    It's already trivial to bypass the abstractness of a class if you really want to instantiate it, so don't bend over backwards making a class abstract if it doesn't really need to be. – chepner Oct 19 '21 at 16:53
  • 1
    What difference is there between a `ConcreteDecorator` class with an empty body and the `Decorator` class you want to be abstract? If there doesn't need to be any new methods added or other behavior changes, why not let `Decorator` instances be created? – Blckknght Nov 02 '21 at 09:41
  • In this specific case an empty decorator would do nothing. Languages such as Java allow signifying that this class is not meant to be instantiated by making the class not concrete. – Gonen I Nov 02 '21 at 09:43
  • 1
    Python is not Java. Don't bend over backwards to forbid something with your class that does no harm and that nobody will want to do anyway. – Blckknght Nov 02 '21 at 20:21

1 Answers1

1

Create a helper class that overrides the __new__ function to check whether cls.__bases__ contains itself.

class UnInstantiable:
    def __new__(cls, *args, **kwargs):
        if __class__ in cls.__bases__:
            raise TypeError(f"Can't instantiate un-instantiable class {cls.__name__}")

        return super().__new__(cls)

Usage:

# class Decorator(Component):                # Add UnInstantiable base class
class Decorator(Component, UnInstantiable):  # like this
    def operation(self) -> None:
        print("basic operation")


Decorator()  # TypeError: Can't instantiate un-instantiable class Decorator
aaron
  • 39,695
  • 6
  • 46
  • 102