【问题标题】:Python type hinting for a generic mutable tuple / fixed length sequence with multiple types具有多种类型的通用可变元组/固定长度序列的 Python 类型提示
【发布时间】:2022-09-23 03:14:34
【问题描述】:

我目前正在为项目添加类型提示,但不知道如何正确处理。我有一个列表列表,嵌套列表包含两个 int 和 float 类型的元素。嵌套列表的第一个元素始终是 int,第二个元素始终是 float。

my_list = [[1000, 5.5], [1432, 2.2], [1234, 0.3]]

我想对它进行类型注释,以便在 for 循环或循环理解中解包内部列表保留类型信息。我可以将内部列表更改为元组,并得到我正在寻找的内容:

def some_function(list_arg: list[tuple[int, float]]): pass

但是,我需要内部列表是可变的。有没有一种很好的方法来为列表执行此操作?我知道像 Sequence 和 Collection 这样的抽象类不支持多种类型。

  • 不,我不认为有任何方法可以用列表来做到这一点。 python类型系统中的列表是同质的
  • 虽然元组不可变,但您的封闭列表是,因此您可以替换整个元组
  • 我没有将其标记为重复,但这是否回答了您的问题? Existence of mutable named tuple in Python?。接受的答案建议使用来自 pypi 的模块 recordclass
  • @Stef 该帖子建议使用我宁愿避免使用的第三方课程。我一直在寻找一个更专注于类型提示的解决方案,具有巧妙的子类或类似的东西。

标签: python data-structures type-hinting python-typing


【解决方案1】:

数据结构的可变性与静态长度和所包含类型的不变顺序不兼容。如果您可以对序列进行排序、附加、前置或插入记录,则无法静态分析序列解包。

想象一下下面的sn-p

def some_function(list_arg: list[int, float]): # Invalid Syntax :)
    myint, myfloat = list_arg # ok?

    list_arg.sort()
    myint, myfloat = list_arg # ??????

    if random.random() < .5:
        list_arg.insert(1, 'yet another type!')
    myint, myfloat = list_arg  # 50% chance of an actual runtime error
                               # fat chance for any static analysis!

如果您的序列可变性是必要的,请为包含的对象的潜在类型编写联合或其他更丰富的类型提示

def some_function(list_arg: list[list[A|B]]): pass

或使用超类型。因为 int 是与 float https://mypy.readthedocs.io/en/latest/duck_type_compatibility.html#duck-type-compatibility 兼容的鸭子类型:

def some_function(list_arg: list[list[float]]): pass

如果您的数据结构实际上不会发生突变,那么选择列表而不是元组是第一个错误。

【讨论】:

    【解决方案2】:

    我认为这个问题突出了静态类型 Python 和动态类型 Python 之间的根本区别。对于习惯于动态键入 Python(或 Perl 或 JavaScript 或任何数量的其他脚本语言)的人来说,在一个列表中有不同的数据类型是完全正常的。它方便、灵活,并且不需要您定义自定义数据类型。但是,当您引入静态类型时,您会进入一个需要更严格设计的更紧凑的盒子。

    正如其他几个人已经指出的那样,列表的类型注释要求列表的所有元素都是相同的类型,并且不允许您指定长度。与其将其视为类型系统的缺点,不如将其视为您自己的设计中的缺陷。你真正要找的是一个有两个数据成员的类。第一个数据成员名为0,类型为int,第二个数据成员名为1,类型为float。作为您的朋友,我建议您定义一个适当的类,并为这些数据成员使用有意义的名称。由于我不确定您的数据类型代表什么,因此我将编造名称以作说明。

    class Sample:
        def __init__(self, atomCount: int, atomicMass: float):
            self.atomCount = atomCount
            self.atomicMass = atomicMass
    

    这不仅解决了打字问题,而且大大提高了可读性。您的代码现在看起来更像这样:

    my_list = [Sample(1000, 5.5), Sample(1432, 2.2), Sample(1234, 0.3)]
    
    def some_function(list_arg: list[Sample]): pass
    

    我确实认为值得强调 Stef 的评论,它指向this 问题。给出的答案突出了与此相关的两个有用功能。

    首先,从 Python 3.7 开始,你可以将一个类标记为数据类,它会自动生成像__init__() 这样的方法。 Sample 类看起来像这样,使用 @dataclass 装饰器:

    from dataclasses import dataclass
    
    @dataclass
    class Sample:
        atomCount: int
        atomicMass: float
    

    该问题的另一个答案提到了一个名为 recordclass 的 PyPi 包,它说它基本上是一个可变的namedtuple。键入的版本称为RecordClass

    from recordclass import RecordClass
    
    class Sample(RecordClass):
        atomCount: int
        atomicMass: float
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-03-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-17
      相关资源
      最近更新 更多