Question

What is mypy list invariance?

Answer and Explanation

In the context of Mypy, a static type checker for Python, "list invariance" refers to how Mypy treats lists with respect to type compatibility. Specifically, it means that a List[T] is not considered a subtype of List[S], even if T is a subtype of S. This behavior is crucial for maintaining type safety and preventing runtime errors.

Let's break down what this means with an example:

Suppose we have two classes, Animal and Dog, where Dog is a subclass of Animal:

class Animal:
  pass

class Dog(Animal):
  pass

Intuitively, you might think that a List[Dog] should be compatible with a List[Animal], since a Dog is an Animal. However, Mypy treats lists as invariant. This means that List[Dog] is not a subtype of List[Animal], and vice versa.

Here's why this is important:

If lists were covariant (meaning List[Dog] was a subtype of List[Animal]), you could do something like this:

from typing import List

def add_cat(animals: List[Animal]) -> None:
  animals.append(Cat()) # Assume Cat is another subclass of Animal

dogs: List[Dog] = [Dog(), Dog()]
add_cat(dogs) # This would be allowed if List was covariant
# Now dogs contains a Cat, which is not a Dog!

This would lead to a runtime error because you'd be adding a Cat to a list that was supposed to contain only Dog objects. Mypy's invariance prevents this by flagging the add_cat(dogs) call as a type error.

Invariance ensures that you can't accidentally introduce incompatible types into a list. If you need to work with a list of a supertype, you should use a different type construct, such as Sequence[Animal] or Iterable[Animal], which are covariant (read-only) in their type parameter.

In summary, Mypy's list invariance is a design choice that prioritizes type safety by preventing the accidental mixing of incompatible types within lists. While it might seem restrictive at first, it helps catch potential bugs at compile time, leading to more robust and reliable code.

More questions