这是一个更详细的互动会话,将帮助我解释正在发生的事情(Python 2.6在Windows XP 32位上,但实际上并不重要)。
>>> import sys
>>> sys.getsizeof([])
36
>>> sys.getsizeof([1])
40
>>> lst = []
>>> lst.append(1)
>>> sys.getsizeof(lst)
52
>>>
请注意,空列表比包含
[1]
的列表要小一些。然而,当添加元素时,它会变得更大。
原因是在CPython源代码中的
Objects/listobject.c
实现细节。
空列表
当创建空列表
[]
时,不会分配元素的空间 - 这可以在
PyList_New
中看到。36字节是32位机器上列表数据结构本身所需的空间量。
具有一个元素的列表
当创建一个只有一个元素
[1]
的列表时,除了列表数据结构本身所需的内存外,还会分配一个元素的空间。同样,在
PyList_New
中可以找到这个信息。给定参数
size
,它计算:
nbytes = size * sizeof(PyObject *);
然后有:
if (size <= 0)
op->ob_item = NULL;
else {
op->ob_item = (PyObject **) PyMem_MALLOC(nbytes);
if (op->ob_item == NULL) {
Py_DECREF(op);
return PyErr_NoMemory();
}
memset(op->ob_item, 0, nbytes);
}
Py_SIZE(op) = size;
op->allocated = size;
所以我们可以看到,当
size = 1
时,会分配一个指针的空间。在我的32位系统上是4个字节。
向空列表添加元素
当在空列表上调用append
时,会发生以下情况:
PyList_Append
调用app1
app1
请求列表的大小(并得到0作为答案)
app1
然后使用size+1
(在我们的例子中是1)调用list_resize
list_resize
有一种有趣的分配策略,源代码中的这个注释对其进行了总结。
下面是注释内容:
new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6);
if (new_allocated > PY_SIZE_MAX - newsize) {
PyErr_NoMemory();
return -1;
} else {
new_allocated += newsize;
}
让我们做些数学题
让我们看看我在文章开头引用的数字是如何得出的。
所以,在32位系统上,列表数据结构本身需要36字节的大小。对于单个元素,会为一个指针分配空间,因此额外需要4个字节,总共40字节。到目前为止没问题。
当对空列表调用app1
时,它将使用size=1
调用list_resize
。根据list_resize
的过度分配算法,1之后最接近的可用大小是4,因此将分配4个指针的空间。4 * 4 = 16字节,36 + 16 = 52。
确实,一切都说得通 :-)