如何类型提示容器子类总是包含特定类型?

How to type-hint that container subclass always contains particular type?

我正在努力让类型提示工作,正如我所期望的那样,关于 PyCharm 中自定义容器子 classes 的内容类型。让我们从一个符合我预期的案例开始。您可以创建 list 的子 class 并指定它将始终包含 int 内容,然后 Pycharm 会识别此类列表中的每个项目都是 int.

class IntList(list[int]): pass
il = IntList(())
answer1 = il[0]

当我将鼠标悬停在 answer1 上时,Pycharm 表示它希望它具有 int 类型,大概是因为 class 声明指定了一个 IntList 不会是任何旧的 list,而是 list[int]。 (不要介意这段代码在 运行 时会引发错误,因为 il 是空的。这只是一个最小的例子,表明 PyCharm 有时可以从中提取类型提示信息class 声明中括号中的 [int]。在从容器中获取项目不会引发错误的其他情况下也会出现同样的问题。)

因此,当从 list 进行子class 时,这工作正常。但我想做的是创建我自己的通用容器 class —— 称之为 Box —— 它可以包含各种不同类型的对象。然后我想声明我自己的 subclass IntBox 将只包含 int 项,我希望 PyCharm 在其各种鼠标悬停提示中识别它,自动 -完成建议和 linting 错误检测,就像 IntList 一样。所以这是我想要的一个非常精简的例子。

class Box(list): pass
class IntBox(Box[int]): pass
ib = IntBox(())
answer2 = ib[0]

在这种情况下,当我将鼠标悬停在 answer2 上时,PyCharm 表示它可能具有类型 Any 并且无法识别 [int] 暗示这不仅仅是一个通用的 Box/list,而是一个内容被类型提示为 int.

我已经尝试了所有我能想到的变化,使用 typing.TypeVartyping.Generic 试图更明确地表明 Box 的每个子 class 将有一个单一类型的内容,即 Box.__getitem__ 将 return 那种类型,而对于子class IntBox 那种类型是 int.

我找到的唯一解决方案是,当我创建 ib 时,我可以明确声明此实例的类型为 IntBox[int],然后 PyCharm 将知道期望 ib[0] 将是 int。但似乎我不需要在每次创建 IntBox 实例时明确说明,而是应该有某种方法可以让 PyCharm 从 [int] 中推断出这一点IntBox 的 class 声明就像 IntList.

的声明一样

当然这只是一个玩具示例。在激发这一点的实际情况下,我希望我的通用容器 class“Box”定义其他方法(不仅仅是 __getitem__),这些方法被类型提示为 return 任何特定类型的对象有问题的“Box”的 subclass 始终包含,其中这在 subclasses 之间有所不同。使用 TypeVarGeneric 我可以让它工作, if 我明确地类型声明每个 subclass 实例将包含一个特定的 [contenttype],但如果没有对每个实例进行繁琐的显式类型声明,我找不到一种方法让它工作。

编辑:因为在告诉 list sub-subclass 中将包含哪些元素的简单情况下有效的解决方案显然不会自动扩展到这种实际情况,这里有一个更接近我需要的例子,包括 Box 是一个嵌套的 Sequence 而不是简单的 list,包括一个 Box.get_first() 方法,它也应该接收 [= 的类型提示14=] 对于 IntBox,包括我认为大致正确的 TypeVar:

from typing import TypeVar, Sequence
T = TypeVar('T')
class Box(Sequence[Sequence[T]]):
    def get_first(self:'Box[T]')->T:
        return self[0][0]
class IntBox(Box[int]): pass
ib = IntBox()  # works only if I declare this is type: IntBox[int]
answer = ib.get_first()  # hovering over answer should show it will be int

进一步编辑:前面的问题似乎是嵌套 Sequence[Sequence[T]]。将其更改为 Generic[T] 会使事情按预期进行。

使用typing.Generic to pass a type to the Box superclass from a subclass using a typing.TypeVar

from typing import Generic, TypeVar

T = TypeVar('T')


class Box(Generic[T], list[T]):
    pass


class IntBox(Box[int]):
    pass


class StrBox(Box[str]):
    pass


ib = IntBox(())
answer1 = ib[0]  # int
sb = StrBox(())
answer2 = sb[0]  # str