mypy:如何将 return 值定义为子 class 实例的列表

mypy: How to define return value as a list of sub-class instances

我正在尝试将方法 foo 的 return 值定义为 AbstractChild 子 class 实例的列表,但 mypy 一直给我一个错误.

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

将 return 类型从列表更改为单个值将使 mypy 停止抱怨,我觉得这很奇怪,但我仍然习惯 python 类型系统,所以我可能缺少一些东西。

mypy 在这里是正确的,因为你的 Parent 没有正确实现 AbstractParent - 为此,它应该定义一个方法 foo return 是 AbstractChildren 的列表,而不是 Children。这是因为集合不是多态的(对于其他语言也是如此,例如 Java):List[AbstractChild]List[Child] 不是同一类型,而 List[Child] 不是不要仅仅因为 Child 继承了 List[AbstractChild]。如果我们没有这个限制,像这样的错误是可能的:

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

(这是 Jon Skeet's answer for a similar question in Java 的改写示例)。

Java,例如,在编译时捕获此类错误并需要显式协方差,例如List<? extends Child> 用于读取列表,List<? super Child> 用于写入列表。

在你的例子中,你也会引入一个通用类型。在下面的示例中,我将 AbstractParent 更改为 return 具有相同类型 CList 元素可以是任何子类 AbstractChildParent 是具有具体子类型 Child:

的泛型 AbstractChild 的具体实现
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 []

有关更多示例,请查看 Generics chapter from mypy docs, in particular the Variance of generic types 部分。