An Example of Covariance, Contravariance and Invariance in Python Type Hints

Date: 12/12/2023 · Tags: #python, #dev, #functional-programming

Qiangning Hong (@hongqn) share a Python type annotation tip on X (Twitter), and raised discussions about covariance, contravariance and invariance in Python

from collections.abc import Sequence

a: list[str] =["a"]

def f1(a: list[int | str]): pass
def f2(a: Sequence[int | str]): pass

f1(a)  # incompatible type assignment
f2(a)  # compatible

So why?

The key point is that list[int | str] is not covariant with list[str], while Sequence[int | str] is covariant with list[str].

The reason of Sequence is immutable and is also covariant, most immutable containers are covariant, so f2 cannot modify the list even if it is passed as a list.

List is mutable, so if the parameter is declared as a list type, list[str] and list[int | str] are two different types. In type system, mutable types usually are invariant (e.g. MutableSequence, list, set in Python).

It would be more clear that we add an function to accept Sequence type, and it will be compatible with variable a:

def f3(a: Sequence[str]): pass

f3(a)  # compatible

Refs: