为何向Python列表追加元素比修改NumPy数组元素更快?
为什么Python列表append比预分配NumPy数组索引赋值更快?
你的测试代码如下:
import numpy as np import time N = 10**8 l = [] L = np.zeros(N) t1 = time.time() for i in range(N): l.append(1) t2 = time.time() print("Adding into list took:", t2 - t1, "s") t1 = time.time() for i in range(N): L[i] = 1 t2 = time.time() print("Adding into array took:", t2 - t1, "s")
测试结果:
Adding into list took: 12.784213542938232 s Adding into array took: 19.65919280052185 s
出现这种反直觉结果的核心原因如下:
1. 列表append的底层预分配优化
Python列表的扩容并非每次追加都重新分配内存,而是采用**平摊O(1)**的预分配策略:每次内存不足时,会额外分配当前容量1.5倍左右的空间(不同实现略有差异)。这意味着绝大多数append操作只是简单的内存写入,开销极低,而且append是Python原生的C级实现,没有额外的类型转换或复杂检查。
2. NumPy数组循环赋值的额外开销
虽然NumPy数组是连续预分配的内存,但Python层面的循环中,每次L[i] = 1都会产生额外开销:
- 需要将Python的整数对象
1转换为NumPy数组默认的float64类型(np.zeros默认 dtype 是float),这个类型转换会消耗时间 - NumPy的索引操作自带边界检查、类型校验等逻辑(为了支持多维数组、切片等复杂功能),即使是单元素索引,这些检查也会带来额外开销
- Python与NumPy底层C代码之间的桥接调用,每次循环都会产生固定的开销
3. NumPy的正确用法:向量化操作
NumPy的优势从来不是Python层面的循环,而是向量化批量操作。如果把循环赋值改成向量化赋值,速度会碾压列表append:
# 替换循环赋值为向量化操作 t1 = time.time() L[:] = 1 t2 = time.time() print("Adding into array via vectorization took:", t2 - t1, "s")
运行这个代码,你会发现耗时通常在0.1秒以内,远快于列表的12秒左右。
4. 数据类型的影响
如果将NumPy数组指定为整数类型(np.zeros(N, dtype=int)),循环赋值的速度会有所提升,但依然不如列表append——因为Python循环对NumPy的操作始终存在桥接开销,而列表append是原生优化的操作。
总结:Python列表的append在循环中表现优异,是因为其底层预分配机制和原生C实现的低开销;而NumPy在Python循环中表现拉胯,是因为每次索引赋值都有额外的类型转换和桥接成本,NumPy的正确打开方式是避免Python循环,使用向量化批量操作。
内容的提问来源于stack exchange,提问作者Syrocco
相关产品推荐
相关产品推荐

