5

I'am trying to define a return value of method foo as a list of AbstractChild sub-class instances, but mypy keeps giving me an error.

class AbstractParent(ABC):
    @abstractmethod
    def foo(self) -> List["AbstractChild"]: pass


class AbstractChild(ABC):
    pass


class Parent(AbstractParent):
    def foo(self) -> List["Child"]: pass
#   ^ mypy: error Return type "List[Child]" of "foo" incompatible with return type "List[AbstractChild]" in supertype "AbstractParent"


class Child(AbstractChild):
    pass

Changing the return type from the list to a single value will make mypy to stop complaining which I find quite strange, but I'm still getting used to python type system so I might be missing something.

jirikuchta
  • 328
  • 2
  • 7

1 Answers1

4

mypy is correct here because your Parent doesn't implement AbstractParent correctly - to do that, it should define a method foo that returns a list of AbstractChildren, not Children. This is because collections are not polymorphic (and this is true for other languages too, e.g. Java): List[AbstractChild] is not the same type as List[Child], and List[Child] doesn't inherit from List[AbstractChild] just because Child does. If we wouldn't have this restriction, errors like this would be possible:

class AbstractChild(ABC):
    pass

class Child(AbstractChild):
    pass

class GrandChild(AbstractChild):
    pass

grandchildren: List[GrandChild] = [GrandChild()]
all_children: List[AbstractChild] = grandchildren
all_children.append(Child())
grandchild: GrandChild = grandchildren[0]  # would pass typechecks but is a Child, actually

(this is a rephrased example of Jon Skeet's answer for a similar question in Java).

Java, for example, catches this type of errors at compilation and requires explicit covariance, e.g. List<? extends Child> for reading from and List<? super Child> for writing to the list.

In your case, you would introduce a generic type as well. In the example below, I change AbstractParent to return a List of elements having the same type C that can be anything that subclasses AbstractChild, and Parent is a concrete implementation of the generic AbstractChild with concrete child type Child:

from typing import List, TypeVar, Generic


C = TypeVar('C', bound='AbstractChild')


class AbstractParent(ABC, Generic[C]):
    @abstractmethod
    def foo(self) -> List[C]: pass


class Parent(AbstractParent["Child"]):
    def foo(self) -> List["Child"]:
        return []

For more examples, check out the Generics chapter from mypy docs, in particular the Variance of generic types section.

hoefling
  • 59,418
  • 12
  • 147
  • 194