mypy:创建一个接受子类实例列表的类型

mypy: creating a type that accepts list of instances of subclasses

假设我有一个 Child class,它是 Parent class 的子 class,以及一个接受实例列表的函数Parent 子classes:

from typing import List


class Parent:
    pass


class Child(Parent):
    pass


def func(objects: List[Parent]) -> None:
    print(objects)


children = [Child()]
func(children)

运行 mypy 在此产生错误:

 error: Argument 1 to "func" has incompatible type "List[Child]"; expected "List[Parent]"

如何为此创建类型?

P.S。有一种方法可以使用 Sequence 类型修复此特定错误:

def func(objects: Sequence[Parent]) -> None:
    print(objects)

但这对其他类似情况没有帮助。我需要 List,而不是 Sequence

从根本上说,在此处传递列表不是类型安全的。例如,如果你这样做呢?

def func(objects: List[Parent]) -> None:
    print(objects)
    objects.append(Parent())

children: List[Child] = [Child(), Child(), Child()]
func(children)
# Uh-oh! 'children' contains a Parent()!

如果允许进行类型检查,您的代码最终将包含错误。

为了使用类型行话,List 被有意设计为 不变的 类型。也就是说,尽管 ChildParent 的子类,但 List[Child] 不是 List[Parent] 的子类,反之亦然。您可以找到有关不变性的更多信息 here and here.

最常见的替代方法是使用 Sequence,这是只读的 interface/protocol/whatever。由于 Sequence 是只读的,因此它是 协变的 是安全的:也就是说,Sequence[Child] 被认为是 Sequence[Parent].[= 的有效子类型31=]

根据您的具体操作,您也许可以使用 type variables。例如。而不是说 "this function takes in a list of Parent",你说 "this function takes in a list of any class which is Parent, or a subclass of Parent":

TParent = TypeVar('TParent', bound=Parent)

def func(objects: List[TParent]) -> List[TParent]:
    print(objects)

    # Would not typecheck: we can't assume 'objects' will be a List[Parent]
    objects.append(Parent())  

    return objects

根据您的具体操作,您可以创建一个 custom Protocol 来定义一个只写列表类集合(或自定义数据结构)。由于您的数据结构是只写的,您可以使它成为 逆变——也就是说,WriteOnlyThing[Parent] 将是 WriteOnlyThing[Child] 的子类型。然后让 func 接受 WriteOnlyThing[Child] 并且可以安全地传入 WriteOnlyThing[Child]WriteOnlyThing[Parent] 的实例。

如果这两种方法都不适用于您的情况,您唯一的办法是使用 # type: ignore 来消除错误(不推荐),放弃对列表内容进行类型检查并使参数为输入 List[Any](同样不推荐),或弄清楚如何重构代码以使其类型安全。