user3517175
user3517175

Reputation: 383

List[Dog] is incompatible with List[Animal] where Dog inherits from Animal

Python 3.6.5 and mypy 0.600

I wrote the code:

from typing import List


class Animal():
    pass


class Dog(Animal):
    def __init__(self) -> None:
        super()

    def bark(self) -> None:
        pass


class Cat(Animal):
    def __init__(self) -> None:
        super()

    def meow(self) -> None:
        pass


arr1: List[Dog] = [Dog(), Dog()]
arr2: List[Animal] = [Dog(), Dog()]

# error: Incompatible types in assignment (expression has type "List[Dog]", variable has type "List[Animal]")
arr3: List[Animal] = arr1

I don't understand, why I have an error 'Incompatible types in assignment ' with a variable 'arr3'. Dog is a class which inherits from a Animal. For example, I don't have an error with variable 'arr2'.

Upvotes: 11

Views: 5323

Answers (2)

user986730
user986730

Reputation: 1396

You can try using Sequence[Animal], which is covariant.

List[T] is invariant; it will only handle items of exactly type T. This means List[Dog] is not a subtype of List[Animal]. This is because of what @awesoon mentioned, which is that it prevents you from accidentally adding items which are incompatible with T:

# this won't compile:

dogs : List[Dog] = [dog1, dog2]
animals : List[Animal] = dogs # compiler error: List is invariant

# if the compiler allowed the previous line,
# then `dogs` would be [dog1, dog2, cat] after the next line
animals.push(cat1) 

On the other hand, Sequence[T] is covariant with T, which means that a Sequence[Dogs] is a subtype of Sequence[Animals]. This is allowed because a Sequence does not have "insert" methods, so you can never accidentally sneak a Cat in a Sequence[Dog]:

dogs : List[Dog] = [dog1, dog2]
animals: Sequence[Animals] = dogs # this is fair game for the compiler
animals.push(cat1) # compiler error: Sequence has no method push
# since Sequences can't add new items, you can't
# accidentally put a cat inside a list of dogs =) 

Upvotes: 6

awesoon
awesoon

Reputation: 33691

Imagine that this would be possible:

arr3: List[Animal] = arr1

Now you think you have list of animals, but this is actually a list of dogs (note that arr3 is not a copy of arr1, they are the same list).

And because you think this is the list of animals you can add a Cat to it.

However, because this is actually list of dogs, you cannot add a Cat to it. Otherwise you will fail on AttributeError after trying to use dog-specific attribute.

More generally, list is invariant - List[Animal] cannot be assigned to List[Dog] (because it can already contain cats) and List[Dog] cannot be assigned to List[Animal] (because you can add cat later)


This might not be obvious in Python, but you can make simple test:

arr3: List[Animal] = arr1 
arr3.append(Cat())
for dog in arr1:
    print(dog.bark())

Mypy does not allow this because this assignment might break your code logic

Upvotes: 10

Related Questions