使用存根文件提供类似 OrderedSet[int] 的类型,而无需修改有序集库

Provide OrderedSet[int] like types using stub file without modifying ordered-set library

contributed type hints for ordered-set 图书馆。问题是尽管我在 ordered_set.pyi 文件中有以下几行:

from typing import MutableSet, TypeVar, Sequence

T = TypeVar('T')

class OrderedSet(MutableSet[T], Sequence[T]):
    ...

我不会写:

IntOrderedSet = OrderedSet[int]

在我的代码中,Python 引发:

TypeError: 'ABCMeta' object is not subscriptable

发生这种情况是因为在 ordered_set.py OrderedSet 中定义为:

from collections.abc import MutableSet, Sequence

class OrderedSet(MutableSet, Sequence):
    ...

我提交了一个 PR,修改了 OrderedSet class to inherit from typing classes,但是 ordered-set 所有者拒绝接受它,因为正如他所说:

Importing typing inside the code adds an install-time dependency and a run-time cost to the code. ordered_set has gotten by as a single module with no dependencies for 6 years. Someone could decide they want nothing to do with setuptools and just drop ordered_set on their PYTHONPATH, and it would still work. I'd hate to lose that over types.

有没有办法在不修改库 ordered_set.py 文件的情况下支持 OrderedSet[int] 类类型?

我知道的唯一解决方法是定义任何自定义类型别名,使它们仅在类型检查时存在,而绝不会在 运行 时存在。

为此,您需要:

  1. if typing.TYPE_CHECKING: 块中定义任何类型别名。 typing.TYPE_CHECKING 块在 运行 时始终为假,类型检查器将其视为真。
  2. 确保您的所有类型提示都是字符串——或者,如果您使用的是 Python 3.7,请添加 from __future__ import annotations,它会自动执行此操作。也就是说,避免首先评估类型提示。

因此,例如,在您的特定设置中,您可以在某处为 OrderedSet 库提供存根,然后将您的代码编写成如下所示(假设 Python 3.7+):

from __future__ import annotations
from typing import TYPE_CHECKING
from ordered_set import OrderedSet

# Basically equivalent to `if False`
if TYPE_CHECKING:
    IntOrderedSet = OrderedSet[Int]

def expects_int_ordered_set(x: IntOrderedSet) -> None:
    # blah

some_ordered_set: IntOrderedSet = OrderedSet()

或者,如果您使用的是 Python 3.6 或更低版本:

from typing import TYPE_CHECKING
from ordered_set import OrderedSet

if TYPE_CHECKING:
    IntOrderedSet = OrderedSet[Int]

def expects_int_ordered_set(x: 'IntOrderedSet') -> None:
    # blah

some_ordered_set: 'IntOrderedSet' = OrderedSet()

如果你不介意撒谎,我们可以省去字符串内容,将 IntOrderedSet 定义为在类型检查时与 运行 时略有不同的东西。例如:

from typing import TYPE_CHECKING
from ordered_set import OrderedSet

if TYPE_CHECKING:
    IntOrderedSet = OrderedSet[Int]
else:
    IntOrderedSet = OrderedSet

def expects_int_ordered_set(x: IntOrderedSet) -> None:
    # blah

some_ordered_set = IntOrderedSet()

尽管这样做时一定要小心 -- 类型检查器不会检查 'else' 中的任何内容 block/won不会检查以确保无论你在做什么与 if TYPE_CHECKING 块中的内容一致。

最终的解决方案是首先不定义 IntOrderedSet 类型。这让我们可以跳过 hack 1,而只需要使用 hack 2(这不算什么 hack —— 几年后 Python 将成为默认行为)。

例如,我们可以这样做:

from __future__ import annotations
from ordered_set import OrderedSet

def expects_int_ordered_set(x: OrderedSet[int]) -> None:
    # blah

some_ordered_set: OrderedSet[int] = OrderedSet()

根据上下文,我什至不需要在最后一个变量声明中添加注释。在这里,我们这样做了,但是如果我们在函数内部定义该变量,则像 mypy 这样的类型检查器可能会根据我们最终使用该变量的方式自动为我们推断出正确的类型。