pythonpython-typingabstract-base-class

Python type annotations for instance of class derived from abstract base class


Assume that an abstract base class MembershipClass has been created. Multiple classes are derived from the abstract base class, e.g., FirstClass, SecondClass, etc.

I wish to use type annotations in a function that accepts as an argument any class derived from MembershipClass. If there are a small number of derived classes (say 2), this should work:

from typing import Union
def MyFunc(membership_obj: Union[FirstClass, SecondClass]) -> None:
   ...

Is there a way to create a type hint for membership_obj which essentially says that its type is any class derived from MembershipClass without having to specify each possible derived class in the type annotation?

I have seen two possible solutions:

  1. TypeVar:
from typing import TypeVar
BaseType = TypeVar('BaseType', bound=MembershipClass)
def MyFunc(membership_obj: BaseType) -> None:
   ...

  1. Direct use of ABC
def MyFunc(membership_obj: MembershipClass) -> None:
   ...

Is either approach acceptable?


Solution

  • It looks like both solutions will work, although the mypy messages are slightly different. Consider the following example (I've added mypy errors inline):

    from abc import ABC
    from typing import TypeVar
    
    
    class Base(ABC):
        pass
    
    
    class Sub(Base):
        pass
    
    
    BaseType = TypeVar("BaseType", bound=Base)
    
    
    def MyFunc(c: Base) -> None:
        pass
    
    
    def MyFunc2(c: BaseType) -> None:
        pass
    
    
    if __name__ == "__main__":
        b = Base()
        s = Sub()
    
        MyFunc(b)
        MyFunc(s)
        MyFunc(3)  # main.py:30: error: Argument 1 to "MyFunc" has incompatible type "int"; expected "Base"
    
        MyFunc2(b)
        MyFunc2(s)
        MyFunc2(3) # main.py:34: error: Value of type variable "BaseType" of "MyFunc2" cannot be "int"
    

    That being said, I think the second method is more readable and intuitive. I think that TypeVar is more suited for generics (that's not to say you shouldn't use it if you want to).