5

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?

rhz
  • 960
  • 14
  • 29

1 Answers1

2

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).

kennyvh
  • 2,526
  • 1
  • 17
  • 26