Variance of generic types

There are three main kinds of generic types with respect to subtyperelations between them: invariant, covariant, and contravariant.Assuming that we have a pair of types A and B, and B isa subtype of A, these are defined as follows:

  • A generic class MyCovGen[T, …] is called covariant in type variableT if MyCovGen[B, …] is always a subtype of MyCovGen[A, …].
  • A generic class MyContraGen[T, …] is called contravariant in typevariable T if MyContraGen[A, …] is always a subtype ofMyContraGen[B, …].
  • A generic class MyInvGen[T, …] is called invariant in T if neitherof the above is true.

Let us illustrate this by few simple examples:

  • Union is covariant in all variables: Union[Cat, int] is a subtypeof Union[Animal, int],Union[Dog, int] is also a subtype of Union[Animal, int], etc.Most immutable containers such as Sequence and FrozenSet are alsocovariant.

  • Callable is an example of type that behaves contravariant in types ofarguments, namely Callable[[Employee], int] is a subtype ofCallable[[Manager], int]. To understand this, consider a function:

  1. def salaries(staff: List[Manager],
  2. accountant: Callable[[Manager], int]) -> List[int]: ...

This function needs a callable that can calculate a salary for managers, andif we give it a callable that can calculate a salary for an arbitraryemployee, it’s still safe.

  • List is an invariant generic type. Naively, one would thinkthat it is covariant, but let us consider this code:
  1. class Shape:
  2. pass
  3.  
  4. class Circle(Shape):
  5. def rotate(self):
  6. ...
  7.  
  8. def add_one(things: List[Shape]) -> None:
  9. things.append(Shape())
  10.  
  11. my_things: List[Circle] = []
  12. add_one(my_things) # This may appear safe, but...
  13. my_things[0].rotate() # ...this will fail

Another example of invariant type is Dict. Most mutable containersare invariant.

By default, mypy assumes that all user-defined generics are invariant.To declare a given generic class as covariant or contravariant usetype variables defined with special keyword arguments covariant orcontravariant. For example:

  1. from typing import Generic, TypeVar
  2.  
  3. T_co = TypeVar('T_co', covariant=True)
  4.  
  5. class Box(Generic[T_co]): # this type is declared covariant
  6. def __init__(self, content: T_co) -> None:
  7. self._content = content
  8.  
  9. def get_content(self) -> T_co:
  10. return self._content
  11.  
  12. def look_into(box: Box[Animal]): ...
  13.  
  14. my_box = Box(Cat())
  15. look_into(my_box) # OK, but mypy would complain here for an invariant type