HOWTO · Python
如何在 Python 中创建具有特定大小的列表
本教程介绍了如何在 Python 中创建具有特定大小的列表
本页内容
当每个位置都应以相同的值开始时,可以使用 [value] * size 创建指定元素数量的列表。例如,[None] * 4 会创建一个包含四个元素的列表。Python 列表保存的是对象引用,因此这会初始化四个条目,而不是预留四个尚未初始化的槽位。应根据每个位置需要包含的内容选择初始化值。
创建固定大小的列表
下面是一个完整示例,它会创建一个包含四个条目、且每个条目都初始化为 None 的列表:
本节中的代码以及显示的标签或输出仍为英文。
size = 4
items = [None] * size
print(len(items), items)
4 [None, None, None, None]
列表的长度为 size,每个条目都引用同一个单例 None。可以通过索引赋值来替换条目,例如 items[0] = "ready"。如果列表长度必须保持不变,应给现有索引赋值,而不要追加或删除元素;不过,Python 列表本身仍然可以调整大小。
当初始值可以安全共享时,使用重复运算简洁明了。遇到可变值时要特别小心:[[]] * 3 会让外层列表中的三个位置都引用同一个内层列表。因此,通过其中一个位置进行的修改,在其他位置也能看到。若要让每个位置都有独立的列表,可以使用列表推导式,例如 [[] for _ in range(3)]。如果每个初始值取决于索引,列表推导式也很实用,例如 [index * 2 for index in range(size)]。
如果构造每个元素需要多个步骤,也可以使用带 append 的循环:先创建 items = [],然后在每次迭代中追加一个值。对于共享的不可变初始值(例如 None),可使用重复运算;对于需要独立创建或计算的值,可使用列表推导式;如果循环能让多步构造更清晰,则可使用循环。这些写法的运算和内存分配方式各不相同,因此不应把特定机器上的旧计时数据当作普遍适用的速度排名。如果性能很重要,应在程序所用的 Python 环境中,使用有代表性的输入比较这些写法。下面的 timeit 脚本会将每种构造方式重复 100 次,报告三次运行中最快的一次,并以秒为单位打印每次构造的耗时;结果只适用于该次运行和对应环境:
import timeit
size = 100
number = 100
cases = {
"repetition": "[None] * size",
"comprehension": "[None for _ in range(size)]",
}
for name, statement in cases.items():
runs = timeit.repeat(
statement,
globals={"size": size},
number=number,
repeat=3,
)
print(f"{name}: {min(runs) / number:.9f} seconds per construction")
若要比较其他任务,请调整值、大小和构造表达式,使其符合程序实际执行的工作。不要把一台机器上的计时结果当作通用排名。
使用 array.array 存储有类型的数值
普通列表可以存放不同类型的值,并保存对 Python 对象的引用。标准库中的 array.array 是一种有类型的序列,适合紧凑存储某一种受支持类型的数值。例如,array('i', [0] * 4) 会创建四个整数条目;使用前应先从 array 模块导入 array。它的类型码会限制可存储的值,因此当列表需要容纳混合对象或一般 Python 值时,不能直接用它替代列表。只有当这种有类型的数值序列适合数据和操作需求时,才应选择它。
使用 NumPy 处理大型数值数组
对于需要对大量值执行数组运算的数值任务,NumPy 提供了 ndarray 类型。numpy.zeros(4) 会创建一个以零初始化的数组,而 numpy.empty(4) 会分配数组,但不会初始化其中的元素。empty 返回的值未指定;读取元素之前,必须先为每个元素赋值。它并不表示“创建一个以零填充的数组”。当 NumPy 的数值数据模型和数组运算适合任务时,它会很有用;但如果只是要创建一个较短的 Python 列表,并不需要额外引入这个依赖。
简而言之,如果列表中的所有位置都应先引用 None,可以使用 [None] * size;如果各位置需要独立值或计算所得的值,可以使用列表推导式;对于合适的单一类型数值序列,可以使用 array.array;需要数值数组运算时,则可以使用 NumPy。应根据程序所需的值和操作来选择,而不是依据固定的大小门槛或其他环境中的基准测试结果。