Python list通过预留空间和动态倍增策略实现扩容:小列表(n

Python list 是如何动态扩容的
Python 的 list 底层用的是 C 数组,但支持动态增删,靠的是「预留空间 + 倍增策略」。每次 append() 时,如果当前容量不够,解释器会分配一块更大的内存(通常是当前容量的 1.125 倍左右),把旧数据拷过去,再追加新元素。
这个倍增系数不是固定 2,而是按当前长度动态计算:当长度 n 时,新容量 = n + 8;当 n >= 64 时,新容量 = n + n // 8(即约 1.125×)。这样既减少小列表的浪费,又避免大列表频繁 realloc。
- 扩容是 amortized O(1),但单次
append()可能触发 O(n) 拷贝 -
extend()批量添加时,也会按目标长度预估并一次性扩容,比循环append()更快 - 用
list *= 2或list += [x] * n不会触发多次扩容,因为 Python 能静态推断最终长度
什么时候扩容会拖慢你的代码
高频小量追加(比如在循环里反复 append() 十万次)看似简单,实际可能触发几十次内存分配和拷贝。尤其当列表从空开始增长时,前几轮扩容虽快,但到几万规模后,每次拷贝几百 KB 甚至 MB 数据,延迟就明显了。
- 典型陷阱:
result = []然后for x in data: result.append(transform(x))—— 如果data很大,不如先result = [None] * len(data)预分配 -
insert(0, x)强制所有已有元素右移,是 O(n) 操作,且几乎每次都会触发扩容(因为头部插入不利用预留空间) -
pop(0)同样要左移,性能差;应改用collections.deque处理队列场景
如何观察和验证扩容行为
不能只看 len(my_list),得看底层分配容量——用 sys.getsizeof() 粗略估算内存占用,或更准地用 array.array 对比,但最直接的是调用 list.__sizeof__() 加上指针开销,不过生产中推荐用 sys.getsizeof() 辅助判断。
立即学习“Python免费学习笔记(深入)”;
功能列表:底层程序与前台页面分离的效果,对页面的修改无需改动任何程序代码。完善的标签系统,支持自定义标签,公用标签,快捷标签,动态标签,静态标签等等,支持标签内的vbs语法,原则上运用这些标签可以制作出任何想要的页面效果。兼容原来的栏目系统,可以很方便的插入一个栏目或者一个栏目组到页面的任何位置。底层模版解析程序具有非常高的效率,稳定性和容错性,即使模版中有错误的标签也不会影响页面的显示。所有的标
更实用的方法是监控 id() 和 len() 变化:
import sys
l = []
for i in range(100):
old_id = id(l)
l.append(i)
if id(l) != old_id:
print(f"len={len(l)-1} → {len(l)}, size={sys.getsizeof(l)} bytes")- 输出会显示扩容发生的临界点(如 0→1、1→2…直到 64→72、72→81…)
-
sys.getsizeof()返回的是对象总内存,包括容器本身和指向元素的指针数组,不包括元素内容(如字符串对象另算) - 注意:CPython 实现细节,PyPy 或其他解释器策略不同,别依赖绝对数值
预分配和替代方案的实际取舍
预分配不是银弹。如果你根本不知道最终长度(比如流式处理、条件过滤),硬写 [None] * estimated_max 可能浪费内存,甚至因估计过大导致 OOM。
- 确定长度 → 用
[None] * n或array.array('i', [0]) * n(更省内存) - 不确定长度但追求速度 → 用
list.extend()替代循环append(),或考虑生成器表达式 +list() - 频繁首尾增删 → 切换为
collections.deque,它用双向链表块实现,无扩容问题 - 纯数值计算 →
array.array或numpy.ndarray更紧凑,且扩容逻辑完全不同(通常不允许动态增长)
真正影响性能的往往不是扩容本身,而是你没意识到它正在发生——尤其是在嵌套循环、回调函数或日志聚合这类“看起来很轻”的场景里。










