This blog post is about covariance, contravariance, and invariance of Python types. I define these concepts and explain them in detail. Every step is accompanied by a fairly straightforward code snippet. I aim to show that the knowledge of these concepts helps to write more reliable code and it is beneficial to all Python programmers.

This is the third part of a blog post series about contravariance and issues related to it. This part is self-contained, though, and can be read separately. The first two parts are devoted to understanding (s02e01) and fixing (s02e02) a specific contravariance-related bug.

If you are new to Python type annotations, I suggest reading my two introductory blog posts first: s01e01 and s01e02.

There are some formalisms in this post, yet they are quite straightforward. I use <: symbol, which reads “is a subtype of” (i.e. subtype is always at the pointy end 🗡 😉). If you are not sure what subtyping is or how it differs from subclassing, just read this and this part of s01e01 blog post.

All code is compatible with Python 3.6+. I use mypy 0.641 type checker.

Covariance

Tuple

Let’s say we have a tuple of Dog s and a tuple of Animal s. Their types are Tuple[Dog, ...] and Tuple[Animal, ...] , respectively (for a reminder about Tuple type, see here). Every object with the type of Tuple[Dog] can be safely assigned to a variable of the type Tuple[Animal] , but not the other way around:

Why the error is reported? It's because animals tuple could contain other animals than dogs. (And in our code in fact it does: an_animal .) Therefore, the type of dogs , which is Tuple[Dog, ...] , could be compromised if we assigned an object of Tuple[Animal, ...] type to it. (And in our code in fact it is assigned.)

On the other hand, mypy is happy with animals = dogs assignment. It’s because every element of dogs is a Dog and, due to tuple’s immutability, cannot be anything else. Therefore, it can be used where an object of the type Animal is expected, as Dog is a subtype of Animal . Thus Tuple[Dog, ...] <: Tuple[Animal, ...] .

More generally:

Tuple[SubType, ...] <: Tuple[SuperType, ...] ,

for SubType <: SuperType .

Similarly, in the case of multiple-type tuples:

Tuple[SubType1, SubType2, etc.] <: Tuple[SuperType1, SuperType2, etc.] ,

for SubType1 <: SuperType1 , SubType2 <: SuperType2 (etc.).

Definition of Covariance

The property of Tuple type we have just discovered is called a covariance. Or, more precisely: Tuple is covariant in all its arguments. The formal definition of the covariance is as follows (I slightly modified the definition from mypy docs):

Generic type GenType[T, ...] is covariant in type variable T

if GenType[SubType, ...] <: GenType[SuperType, ...] ,

for SubType <: SuperType .