Python列表扩容触发条件是当前元素个数等于已分配容量;首次分配allocated为8,后续按约12.5%增量(old_size + old_size>>3 + 1)增长,兼顾效率与内存利用率。

Python列表追加元素时触发扩容,是因为底层用的是动态数组,而动态数组的存储空间是预先分配的固定大小——append() 一旦试图往“已满”的底层数组里塞新元素,就必须申请更大内存并复制数据。
扩容触发的精确条件是什么
不是“只要调用 append() 就扩容”,而是当列表当前元素个数(ob_size)等于已分配容量(allocated)时才触发。换句话说:只有“没空位了”才会扩容。
- 空列表
[]第一次append()后,allocated变为 1(不是 0) - 容量为 8 的列表,插入第 9 个元素时触发扩容(因为
ob_size == 8且allocated == 8) -
len(lst) == 100并不意味着allocated == 100;实际allocated通常更大,比如 113
CPython 实际扩容公式怎么算
不是简单翻倍,而是用近似 12.5% 增量的策略,兼顾小容量响应快、大容量不浪费。核心逻辑在 list_resize() 函数中:
- 若
allocated == 0(首次分配),直接设为 8 - 否则,新容量 ≈
old_size + (old_size >> 3) + (old_size - 等价于:new_size = old_size + old_size // 8 + offset(offset 小容量取 3,大容量取 6)
- 例如:从 64 扩到 73(64 + 8 + 1),从 1000 扩到 1127(1000 + 125 + 2)
为什么不用固定倍数(比如 2x)
固定倍数会导致内存浪费严重,尤其对中等规模列表。比如从 1000 扩到 2000,多占了 1000 个指针空间(约 8KB),但后续可能只加几十个元素。
立即学习“Python免费学习笔记(深入)”;
- 1.125x 增量让均摊开销更平滑,
append()均摊时间复杂度稳定为 O(1) - 但代价是扩容更频繁——不过每次复制量小,总开销反而更低
- 这个策略在 CPython 源码中硬编码实现,不同 Python 实现(如 PyPy)可能不同
哪些操作会意外触发扩容
除了 append(),以下操作同样可能触发扩容,但容易被忽略:
-
insert(i, x):当i超出当前长度时,行为等价于append(),也会检查容量是否足够 -
lst += [x]或lst.extend([x]):内部仍走append循环,每轮都判断容量 -
list * n(重复):如果原列表非空,结果列表长度变大,构造时就按目标长度预分配,不走增量扩容
真正容易踩坑的是:反复 insert(0, x) ——它不常扩容,但每次都要移动全部元素,性能瓶颈其实在数据搬移,不在扩容本身。


















