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

Posted

技术标签:

【中文标题】mypy:创建一个接受子类实例列表的类型【英文标题】:mypy: creating a type that accepts list of instances of subclasses 【发布时间】:2021-12-12 16:09:02 【问题描述】:

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

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]"

如何为此创建类型?

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

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

但这在其他类似情况下无济于事。我需要List,而不是Sequence

【问题讨论】:

【参考方案1】:

在这里传递一个列表基本上不是类型安全的。例如,如果你这样做呢?

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()!

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

为了使用 type-jargon,List 被有意设计为 invariant 类型。也就是说,即使ChildParent 的子类,List[Child] 也不是List[Parent] 的子类,反之亦然。您可以找到更多关于不变性here 和here 的信息。

最常见的替代方法是改用Sequence,这是一个只读接口/协议/任何东西。由于 Sequence 是只读的,因此它是安全的协变:也就是说,Sequence[Child] 被认为是Sequence[Parent] 的有效子类型。

根据您的具体操作,您也许可以改用type variables。例如。而不是说“这个函数接受一个父列表”,而是说“这个函数接受一个任何父类的列表,或者父类的子类”:

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] 类型的参数(也不推荐),或者弄清楚如何重构代码,使其类型安全。

【讨论】:

为了使其类型安全,您还可以使用Tuple 而不是List。不能追加到元组,所以@Michael0x2a 描述的问题就没有了。 @pawelswiecki -- 你可以使用元组,当然,或任何协变数据结构,但我不认为在这种情况下使用元组真的比在这种情况下使用序列更能买到。 Tuple 是 Sequence 的子类,并且只添加了一些额外的特定于 tuple 的方法,例如 __lt__,我相信这些方法都不是由列表实现的。如果 OP 的原始代码使用列表,那么他们一开始就使用这些特定于元组的方法。那么为什么不使用更通用的类型而只使用序列呢? 如果元组没问题(如果没有更多上下文很难判断)我会使用它,因为它比使用TypeVar 简单得多。 (列表确实实现了__lt__,至少你可以比较列表。) Typevar 正是我想要的。我看到了文档,只是无法理解“上限”概念。谢谢! @Michael0x2a 请看一下这个问题***.com/questions/53316262/… ,它是我所有上下文问题的扩展完整版本。非常感谢!

以上是关于mypy:创建一个接受子类实例列表的类型的主要内容,如果未能解决你的问题,请参考以下文章

Mypy 子类中更具体的参数

一种将 NamedTuple 子类化以进行类型检查的方法

mypy 错误:向抽象方法添加类型时无法使用抽象属性实例化抽象类

使用“类”数据类型时,如何指定类型以便只接受特定类的子类?

11.5K Star,一个开源的 Python 静态类型检查库

为啥接口类型列表不能接受继承接口的实例? [复制]