python列表预分配[none] * n比连续append()快2–3倍,因其避免动态扩容的多次内存拷贝;但需预先知道长度,且仅适用于可预测大小的场景。

Python列表扩容机制导致多次内存拷贝
CPython 的 list 是动态数组,底层用连续内存块存储对象指针。但它的容量(allocated)通常大于当前元素数(ob_size)。每次 append() 时,如果容量不足,就要:① 分配新内存(按公式 new_allocated ≈ old_size * 9//8 + 6),② 把旧数据逐个复制过去,③ 释放旧内存。这个过程在 1000 次追加中可能触发 10+ 次,每次复制开销随已存元素线性增长。
[None] * n 预分配绕过所有动态扩容
当你写 l = [None] * 1000,CPython 直接分配刚好容纳 1000 个指针的连续内存块,allocated == ob_size == 1000。后续用索引赋值(如 l[i] = value)不触发任何扩容逻辑——没有 realloc,没有 memcpy,只有纯指针写入。实测对 10⁵ 元素,比连续 append() 快 2–3 倍。
- 必须提前知道最终长度,否则预分配后还要
del或切片,反而更慢 -
[None] * n只适用于可变对象引用场景;若存的是不可变小整数(如0),CPython 可能复用同一对象,但语义安全,不影响性能 - 不要用
list(range(n))替代——它本质仍是 n 次append(),没跳过扩容
和列表推导式、array.array 的适用边界
预分配不是万能解法:[i**2 for i in range(1000)] 在大多数情况下比预分配 + 循环更快,因为推导式由 C 层实现,且避免了 Python 字节码循环开销。但如果你需要在循环中做复杂判断或调用外部函数,预分配就更可控。
- 纯数值密集计算 → 优先用
array.array('i', [0]) * n或 NumPy,内存更紧凑(存值而非指针) - 混合类型或需动态修改 → 预分配
list仍是最简单有效的手段 - 写入顺序非线性(如随机索引赋值)→ 预分配依然有效,而
append()完全失效
容易被忽略的细节:None 不是占位符,而是真实对象引用
[None] * n 创建的每个位置都持有对同一个 None 对象的引用,不是“空槽”。这没问题,因为后续赋值会覆盖指针。但若误以为这是“未初始化状态”,并在循环里漏掉某次赋值,就会留下 None ——这不是内存问题,而是逻辑错误。真正要注意的是:预分配只优化内存分配路径,不改变 Python 的引用语义。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











