Question
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.