为何Python列表推导比预初始化大小的列表更快?
为什么列表推导式比预分配大小的列表初始化更快?
先看三个测试用的Python函数:
def without_initialising_size(): x = [] for i in range(100_000): x.append(i) return x def with_initialise_size(): x = [None] * 100_000 for i in range(100_000): x[i] = i return x def list_comprehension(): return [i for i in range(100_000)]
已知with_initialise_size比without_initialising_size更快,原因是预先确定列表大小后,Python无需在列表增长时反复寻找更大的内存块。但为什么list_comprehension比with_initialise_size快得多?
以下是使用timeit.timeit(number=1000)的基准测试结果:
without_initialising_size:3.85671530000036with_initialise_size:3.0115716999971482list_comprehension:2.278098400001909
是不是因为with_initialise_size多了为每个列表元素加载None的步骤,还是有其他被忽略的因素?
核心原因解析
主要有两个关键因素导致列表推导式更快:
避免预填充
None的双重赋值开销[None] * 100_000会先创建一个所有元素都是None的列表,这一步需要遍历整个内存块写入None的引用。之后的for循环还要把每个位置的None替换成i,等于对每个列表位置执行了两次赋值操作——先写None,再写目标值。而列表推导式是直接将i一次性写入对应位置,完全没有这层额外的预填充步骤。列表推导式的底层优化更彻底
列表推导式是Python专门优化的语法,它的执行逻辑在解释器底层直接完成,规避了普通for循环的不少额外开销:- 不需要在每次循环中查找调用
append方法,或者执行列表下标赋值的字节码操作; - 推导式的变量查找、循环迭代逻辑都做了内联优化,减少了Python解释器在字节码层面的调度成本;
- 推导式可以直接获取
range的长度,预先分配刚好足够的内存,既不需要像append那样预留额外空间,也不需要预填充None。
- 不需要在每次循环中查找调用
简单来说,列表推导式把循环和列表构建的逻辑合并成了更紧凑、更贴近底层的操作,而预分配列表的方式还多了一层预填充和二次赋值的成本,自然速度会落后。
内容的提问来源于stack exchange,提问作者Salaah Amin
相关产品推荐
相关产品推荐

