Python列表是动态指针数组,扩容按1.125倍增长;append()逐个添加最坏O(N log N),extend()预分配空间为O(N),实测快约6倍;应据数据形态选方法,生成器无法触发extend优化。

Python列表的底层内存是怎么分配的
Python列表不是简单的连续数组,而是一个指向对象指针的动态数组。每次调用 list.append() 或 list.extend() 时,CPython会检查当前容量是否足够;不够就触发扩容,新容量通常是旧容量的1.125倍(向上取整),并重新分配内存、拷贝原有指针。
这意味着频繁小量追加会产生多次内存重分配和指针拷贝——尤其在预估不准长度时,性能损耗明显。
append() 为什么比 extend() 慢得多(当批量添加时)
对单个元素,append() 是 O(1) 均摊;但对 N 个元素逐个 append(),最坏情况触发 O(log N) 次扩容,总时间接近 O(N log N)。而 extend() 接收可迭代对象后,会先计算总长度(若支持 __len__),一次性预分配足够空间,再批量拷贝指针,总时间稳定在 O(N)。
- 常见错误:用
for item in items: my_list.append(item)替代my_list.extend(items) - 注意:如果
items是生成器(无__len__),extend()会退化为逐个追加,失去优势 -
extend()内部仍调用 C 函数list_extend,对 list/tuple 等有长度的对象做了专门优化
实测性能差异有多大
在 CPython 3.12 下,向空列表添加 10 万个整数:
立即学习“Python免费学习笔记(深入)”;
import timeitsetup = "x = []" append_stmt = "for i in range(100000): x.append(i)" extend_stmt = "x.extend(range(100000))"
t1 = timeit.timeit(append_stmt, setup=setup, number=10000) t2 = timeit.timeit(extend_stmt, setup=setup, number=10000)
print(f"append: {t1:.4f}s") print(f"extend: {t2:.4f}s")
典型输出:append ≈ 1.8s,extend ≈ 0.3s(快约6倍)
差距随数据量增大而拉大。如果目标列表已有大量元素,且扩容频繁,append() 的劣势更显著。
什么时候该用 append(),什么时候必须 extend()
选哪个不取决于“习惯”,而取决于你手头的数据形态和控制粒度:
- 用
append():明确只加一个元素;或需在循环中穿插逻辑(如条件过滤、类型转换) - 用
extend():已有一个现成的序列(list、tuple、range)、且无需中间处理 - 避免陷阱:不要对生成器用
extend()期望它“变快”——它无法预知长度,实际行为等价于多次append() - 进阶技巧:若知道最终长度,可先
my_list = [None] * N预分配,再用索引赋值,比两者都快(但牺牲了动态性)
真正容易被忽略的是:即使你写了 extend(some_list),如果 some_list 是刚从 map() 或自定义生成器来的,它依然不会预分配——得自己判断可迭代对象是否支持 len()。











