Search code examples
pythontypesabstract-classmypypython-3.9

Force an abstract class attribute to be implemented by concrete class


Considering this abstract class and a class implementing it:

from abc import ABC

class FooBase(ABC):
    foo: str
    bar: str
    baz: int

    def __init__(self):
        self.bar = "bar"
        self.baz = "baz"

class Foo(FooBase):
    foo: str = "hello"

The idea here is that a Foo class that implements FooBase would be required to specify the value of the foo attribute, but the other attributes (bar and baz) would not need to be overwritten, as they're already handle by a method provided by the abstract class.

From a MyPy type-checking perspective, is it possible to force Foo to declare the attribute foo and raise a type-checking error otherwise?

EDIT:

The rationale is that FooBase is part of a library, and the client code should be prevented from implementing it without specifying a value for foo. For bar and baz however, these are entirely managed by the library and the client doesn't care about them.


Solution

  • This is a partial answer. You can use

    class FooBase(ABC):
        @property
        @classmethod
        @abstractmethod
        def foo(cls) -> str:
            ...
    
    class Foo(FooBase):
        foo = "hi"
    
    def go(f: FooBase) -> str:
        return f.foo
    

    It's only partial because you'll only get a mypy error if you try to instantiate Foo without an initialized foo, like

    class Foo(FooBase):
        ...
    
    Foo()  # error: Cannot instantiate abstract class "Foo" with abstract attribute "foo"
    

    This is the same behaviour as when you have a simple @abstractmethod. Only when instantiating it is the error raised. This is expected because Foo might not be intended as a concrete class, and may itself be subclassed. You can mitigate this somewhat by stating it is a concrete class with typing.final. The following will raise an error on the class itself.

    @final
    class Foo(FooBase):  # error: Final class __main__.Foo has abstract attributes "foo"
       ...