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
ifMyCovGen[B, …]
is always a subtype ofMyCovGen[A, …]
. - A generic class
MyContraGen[T, …]
is called contravariant in typevariableT
ifMyContraGen[A, …]
is always a subtype ofMyContraGen[B, …]
. - A generic class
MyInvGen[T, …]
is called invariant inT
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 subtypeofUnion[Animal, int]
,Union[Dog, int]
is also a subtype ofUnion[Animal, int]
, etc.Most immutable containers such asSequence
andFrozenSet
are alsocovariant.Callable
is an example of type that behaves contravariant in types ofarguments, namelyCallable[[Employee], int]
is a subtype ofCallable[[Manager], int]
. To understand this, consider a function:
- def salaries(staff: List[Manager],
- 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:
- class Shape:
- pass
- class Circle(Shape):
- def rotate(self):
- ...
- def add_one(things: List[Shape]) -> None:
- things.append(Shape())
- my_things: List[Circle] = []
- add_one(my_things) # This may appear safe, but...
- 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:
- from typing import Generic, TypeVar
- T_co = TypeVar('T_co', covariant=True)
- class Box(Generic[T_co]): # this type is declared covariant
- def __init__(self, content: T_co) -> None:
- self._content = content
- def get_content(self) -> T_co:
- return self._content
- def look_into(box: Box[Animal]): ...
- my_box = Box(Cat())
- look_into(my_box) # OK, but mypy would complain here for an invariant type