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 AbstractChild
ren, not Child
ren. 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.