我目前正在努力向项目添加类型提示,但不知道如何做到这一点。我有一个列表列表,其中嵌套列表包含两个 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 和动态类型 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
数据结构的可变性与所包含类型的静态长度和不变顺序不兼容。如果您可以对序列拆包进行排序、追加、前置或插入记录,则无法静态分析序列拆包。
想象下面的片段
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
如果您的数据结构实际上不会发生变化,那么选择列表而不是元组是第一个错误。
对
TallChuck
的回复的一个小补充:
from recordclass import dataobject
class Sample(dataobject):
atomCount: int
atomicMass: float
类
Sample
不支持类似namedtuple
的API。但它支持某种类似 dataclasses
的 API。