Search code examples
pythonabstract-class

Python check attribute type in inherited abstract class


I have a basic abstract class structure such as the following:

from abc import ABCMeta, abstractmethod

class BaseClass(metaclass=ABCMeta):
   @property
   @abstractmethod
   def class_name(self):
       pass

   @property
   @abstractmethod
   def class_val(self):
       pass

   @abstractmethod
   def foo(self):
       pass

   def bar(self):
       # do something


class ChildClass1(BaseClass):
    class_name = "Child1"
    class_val = 1

    def foo(self):
        # do something else


class ChildClass2(BaseClass):
    class_name = "Child2"
    class_val = 2

    def foo(self):
        # do something else again

This works to basically do what I want, i.e., it lets me define variations of a base class which share the same implementation of the method bar() but have different implementations of foo(), with different class-specific attributes -- all of which must be implemented in the child classes, otherwise it raises the error TypeError: Can't instantiate abstract class ChildClass1 with abstract methods class_name (for example, if I try implement it without the class_name class attribute).

This is almost what I want. However I would like to know if there is any way I can force the child classes to have a string type for the class_name attribute, an int type for the class_val attribute, etc.?


Solution

  • The best thing to do would be to use type hints, and then validate the type hints using a third party tool such as mypy:

    class BaseClass(metaclass=ABCMeta):
       @property
       @abstractmethod
       def class_name(self) -> str:
           pass
    
       @property
       @abstractmethod
       def class_val(self) -> int:
           pass
    
       @abstractmethod
       def foo(self):
           pass
    
       def bar(self):
           pass
    

    If you try to define a subclass with the wrong types for the properties, mypy will throw an error, e.g. with:

    class ChildClass2(BaseClass):
        class_name = "Child2"
        class_val = "a"
    
        def foo(self):
            pass
    

    you'd get the error along the lines of:

    test.py: error: Incompatible types in assignment
    (expression has type "str", base class "BaseClass" defined the type as "int")
    

    This technically doesn't stop someone from defining a subclass that has the wrong type, since the Python runtime ignores type hints. However, an approach that raises an error if a bad type is encountered would likely need a call to isinstance() for each instance variable, which would become unwieldy if you have a lot of properties that you want to enforce the types for.