使用Python typing模块指定序列或列表的长度

82

我正在尝试使用Python中的typing模块。

我知道可以像下面这样指定List的长度*:

List[float, float, float]   # List of 3 floats <-- NOTE: this is not valid Python

有没有更长列表的速记?如果我想设置为10个浮点数怎么办?
List[float * 10]   # This doesn't work.

不知道这是否可行,这将非常方便。


*注意:目前以这种方式向Sequence[](及其子类)提供多个参数是无效的Python。此外,使用typing模块以这种方式指定Sequence长度目前也不可能。

4个回答

64

你不能这样做。列表是一种可变的、长度可变的结构。如果你需要一个固定长度的结构,请使用元组:

Tuple[float, float, float, float, float, float, float, float, float, float]

或者更好的是,使用命名元组,它既有索引又有命名属性:

class BunchOfFloats(NamedTuple):
    foo: float
    bar: float
    baz: float
    spam: float
    ham: float
    eggs: float
    monty: float
    python: float
    idle: float
    cleese: float

列表只是一种不适合固定长度数据结构的数据类型。


4
如果你正在使用元组,你也可以使用字面省略号,即 Tuple[int, ...],参考 PEP484 - Tomasz Bartkowiak
19
@TomaszBartkowiak说的是相反的意思。是的,你可以这样声明一个包含单一类型的可变长度元组。但那不是一个固定大小的元组。 - Martijn Pieters
17
有时候你需要一个可变的容器,但长度是固定的。比如,如果你想要将容器中的项目初始化为 None,然后再用新值更新项目。但是容器的大小仍然保持不变。 - Matt
5
@Matt: 当然,但是Python没有内置的类型可以做到这一点,因此也没有类型提示。 - Martijn Pieters
1
Typescript允许您这样做(即将数组提示为元组),并且它可以正常工作。实际上,这真的很烦人,因为Python用户经常*将列表用作元组,我真的很想能够向它们添加类型提示。 - Timmmm
@Timmmm:这是因为JavaScript 没有元组,所以Typescript不得不从头开始发明它们。 - Martijn Pieters

38

typing.Annotated 在这里非常方便。它允许您为类型提示指定任意元数据:

Annotated[list[float], 3]

如果你是第一次使用Annotated,以下是文档中的一段摘录:

如果一个库(或工具)遇到类型提示Annotated[T, x],并且没有特殊的元数据逻辑x,它应该忽略它,并简单地将类型视为T

值得注意的是,mypy有一个未解决请求(截至2022年11月仍然开放),希望实现类似于这样的功能。与此同时,请将Annotated视为开发人员可读性的体现,而不是自动化检查的工具(除非你正在开发检查工具)。


8
谢谢,知道typing.Annotated的存在很好。 我认为答案应该解释一下添加的整数“3”就像附加到注释变量上的注释一样工作。 您必须编写自己的工具来实际使用额外的注释。此外,仅添加一个整数会使额外的注释含义不明确。最好创建一个结构来能够表达上下文 - 类似于: Annotated[List[float],Length[3]] - pabouk - Ukraine stay strong
1
我没有看到 typing.Length - 它实际上存在吗,还是你只是表达了对新功能的渴望? - Addison Klinke
@AddisonKlinke 这是一个类似于文档中使用的ValueRange的自定义类。 - undefined

12

到目前为止,只有元组支持指定固定数量的字段,并且没有固定数量重复的快捷方式。

以下是typing模块中的定义和文档字符串:

class Tuple(tuple, extra=tuple, metaclass=TupleMeta):
    """Tuple type; Tuple[X, Y] is the cross-product type of X and Y.

    Example: Tuple[T1, T2] is a tuple of two elements corresponding
    to type variables T1 and T2.  Tuple[int, float, str] is a tuple
    of an int, a float and a string.

    To specify a variable-length tuple of homogeneous type, use Tuple[T, ...].
    """

    __slots__ = ()

    def __new__(cls, *args, **kwds):
        if _geqv(cls, Tuple):
            raise TypeError("Type Tuple cannot be instantiated; "
                            "use tuple() instead")
        return _generic_new(tuple, cls, *args, **kwds)

因为列表是一种可变长度的类型,所以使用类型声明来指定固定大小是没有意义的。


谢谢Raymond,讲得很清楚。虽然我在这里收到的两个答案都是准确和澄清的,但我仍然不确定提示需要设置长度序列输入的函数的最佳方法。我想只是把这个放在docstring中也不太糟糕,但那似乎有点可惜。(我真的很喜欢PyCharm在每个方法的生成帮助中捕捉这些提示的方式) - John Brodie
2
到目前为止...是否有计划在某个时候在typing模块中指定一个固定长度的可变序列Generic - Rick
3
由于列表是一种可变的、可变长度的类型,使用类型声明来指定固定大小就没有任何意义。但是有时候人们可能想要保持一个固定大小的列表,仅仅是为了可变性(即能够原地更新元素)。在我看来,这是完全有道理的。一个简单的例子:表示范围 [start, stop] 的列表,其中边界是可更新的。 - Eugene Yarmash

6
当我遇到同样的问题时,看到Martijn Pieters answer并不开心。因为我想要一种“快速”和“简单”的解决方法。所以我首先尝试了这里列出的其他建议。
注意:我使用的是带有Pylance作为语言服务器的VSCode。 Zaffys answer是我最喜欢的。
def demystify(mystery: Annotated[Tuple[int], 6]):
    a, b, c, d, e, f = mystery
    print(a, b, c, d, e, f)

函数的提示应该像这样:demystify: (mystery: Tuple[int]) -> None 同时,我收到了一个Pylance错误,Tuple size mismatch: expected 6 but received,出现在a, b, c, d, e, f = mystery这一行。

接下来,我尝试了Tuple[6 * (int, )],这是Martijn Pieters answer评论中balu提到的。

def demystify(mystery: Tuple[6 * (int,)]):
    a, b, c, e, f, g = mystery
    print(a, b, c, e, f, g)

导致与之前相同的Pylance错误。 该函数的提示是:demystify: (mystery: Tuple[Tuple[Type[int], ...]]) -> None 回到记录预期长度的步骤:
def demystify(mystery: Tuple[int, int, int, int, int, int]):
    a, b, c, e, f, g = mystery
    print(a, b, c, e, f, g)

这解决了Pylance错误,并为我提供了一个“clear”函数提示:demystify: (mystery: Tuple[int, int, int, int, int, int]) -> None 但就像约翰·布罗迪一样,我对这个解决方案并不满意。
现在回到起初不想要的答案:
class MysteryType(NamedTuple):
    a: int
    b: int
    c: int
    d: int
    e: int
    f: int
    g: int

def demystify(mystery: MysteryType):
    print(*mystery)

现在函数提示看起来更神秘了:demystify: (mystery: MysteryType) -> None,但创建一个新的MysteryType会给我所有需要的信息:(a: int, b: int, c: int, d: int, e: int, f: int, g: int)

此外,我可以在其他方法和函数中使用MysteryType,而无需计算类型提示。

因此,为了简短地概括并转述Python之禅:

命名元组是一个非常好的想法 - 让我们做更多这样的事情!


3
Annotated[Tuple[int], 6] 表示一个带有单个 int 的元组(元数据为 6)。Zaffy 的答案是 Annotated[List[int], 6],这是一个任意的 int 列表(元数据为 6)。理想情况下,类型检查器应该能够读取 6 并理解您想要一个固定大小的列表,但这不是一种标准的指定方式。 - Nulano
感谢您指出这一点。当使用 Annotated[List[int], 6] 时,当然不会显示错误。但是,在使用 Pylance 作为语言服务器的 VSCode 中,我仍然无法获得适当的类型提示。因此,我仍然会坚持使用 NamedTuple 解决方案。但是,Annotated[List[int], 6] 在其他代码编辑器中可能效果很好。 - Alex

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接