Python列表Capacity(容量)分配机制解析:创建与修改规则
问题背景
需要明确Python在创建列表(手动字面量、list()构造函数、列表推导式等方式)及修改列表时,如何决定底层的容量(Capacity)。以下是测试代码及输出,其中列表推导式的容量结果与其他方式存在差异,引发疑问:
测试代码
from sys import getsizeof empty_list = [] list_base_size = getsizeof(empty_list) print(f"{list_base_size = } bytes.") print() list_manually = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 ] list_manually_size = getsizeof(list_manually) list_manually_capacity = (list_manually_size - list_base_size) // 8 print(f"{list_manually_size = } bytes.") print(f"{list_manually_capacity = } elements.") print() list_constructor = list(range(20)) list_constructor_size = getsizeof(list_constructor) list_constructor_capacity = (list_constructor_size - list_base_size) // 8 # list capacity should also be equal to : # from cpython # new_allocated = (size_t)newsize + (newsize >> 3) + (newsize < 9? 3 : 6); print(f"{list_constructor_size = } bytes.") print(f"{list_constructor_capacity = } elements.") print() list_comprehension = [i for i in range(20)] list_comprehension_size = getsizeof(list_comprehension) list_comprehension_capacity = (list_comprehension_size - list_base_size) // 8 print(f"{list_comprehension_size = } bytes.") print(f"{list_comprehension_capacity = } elements.")
输出结果
list_base_size = 56 bytes. list_manually_size = 216 bytes. list_manually_capacity = 20 elements. list_constructor_size = 216 bytes. list_constructor_capacity = 20 elements. list_comprehension_size = 248 bytes. list_comprehension_capacity = 24 elements.
核心解析
首先明确:列表的size是实际存储的元素数量,capacity是底层预分配的存储空间能容纳的最大元素数。getsizeof()返回的是列表对象的总内存占用,包含基础结构(56字节)和存储元素指针的数组,因此用(总大小 - 基础大小) // 8(64位系统中每个指针占8字节)计算容量的方式是正确的。
1. 不同创建方式的容量逻辑
手动字面量创建
当使用[0,1,...,19]这种字面量创建列表时,CPython编译器在解析阶段就已经明确知道元素总数,因此会精确分配刚好容纳N个元素的容量,不会预留额外空间。这就是为什么手动创建的20元素列表容量为20。
list()构造函数创建
当list()接收的是实现了__len__方法的可迭代对象(如range、tuple)时,CPython会先获取对象的长度N,然后同样精确分配N个元素的容量,无需额外预留。因此list(range(20))的容量也是20。
列表推导式创建
列表推导式的执行逻辑是逐个向空列表中追加元素(类似调用list.append()),属于未知最终长度的创建场景,因此会触发CPython的动态扩容机制:每次当元素数量达到当前容量时,按照预设公式计算新的容量并分配内存。
扩容的核心公式(来自CPython源码):
new_allocated = (size_t)newsize + (newsize >> 3) + (newsize < 9? 3 : 6);
其中newsize是当前需要存储的元素数量:
- 当元素数小于9时,扩容后容量 = 当前元素数 + 当前元素数//8 + 3
- 当元素数大于等于9时,扩容后容量 = 当前元素数 + 当前元素数//8 + 6
对于20元素的推导式,在迭代追加过程中会逐步扩容,最终容量停留在24(不同CPython版本可能有细微差异,但核心逻辑一致)。
2. 修改列表时的容量变化
当通过append()、extend()等方法修改列表时,CPython会先检查当前元素数是否等于容量:
- 若未达到容量,直接添加元素,容量不变
- 若已达到容量,触发扩容,使用上述公式计算新容量并分配内存
例如,空列表的扩容过程:
- 添加第1个元素:容量从0扩容到4(1+0+3)
- 添加到第4个元素:容量满,扩容到10(4+0+6)
- 添加到第10个元素:容量满,扩容到17(10+1+6)
- 添加到第17个元素:容量满,扩容到25(17+2+6),以此类推
总结
- 已知长度的创建方式(字面量、
list()接收已知长度可迭代对象):精确分配与元素数相等的容量,无额外预留 - 未知长度的创建方式(列表推导式、逐个
append、list()接收未知长度可迭代对象):采用动态扩容策略,按公式预分配容量,预留额外空间减少扩容开销 - 修改列表时,元素数达到当前容量即触发扩容,遵循统一的扩容公式
内容的提问来源于stack exchange,提问作者Naughty Constrictor

