Why can I put a cat in a list of animals?
Question
If list
s are invariant,
how come it's legal to append
a Cat
to a list[Animal]
?
class Animal:
def speak(self) -> None:
print("*generic animal noises*")
class Cat(Animal):
def speak(self) -> None:
super().speak()
print("also: meow")
animals: list[Animal] = [Animal(), Animal()]
animals.append(Cat())
Doesn't this require list
to be covariant?
A list
being invariant only means that you can't put a list of cats into a variable which stores
a list of animals (or vice versa).
It's still legal to put a cat into a variable which stores an animal:
and this is roughly what happens when you callanimals.append(Cat())
: animals.append
expects
an animal, and a cat is a perfectly fine animal:
Does this break anything?
list
is invariant because if it weren't, bad things could happen:
def add_pets(animals: list[Animal]) -> None:
animals.append(Dog())
animals.append(Dragon())
cats: list[Cat] = []
animals: list[Animal] = cats # this is illegal!
add_pets(animals) # ...because this would make the cats very uncomfortable
But if you put a Cat
into a list of Animals
, everything will be fine. You can only extract
an Animal
from the list, and a Cat
will work whenever an Animal
works (at least it should).