我将深入研究CPython代码库,以便我们可以看到实际计算的大小。 在您的具体示例中,没有执行过度分配,因此我不会涉及到这一点。
我将在这里使用64位值。
Py_SIZEs的大小由以下函数Py_SIZE计算:
static PyObject *
list_sizeof(PyListObject *self)
{
Py_ssize_t res;
res = _PyObject_SIZE(Py_TYPE(self)) + self->allocated * sizeof(void*);
return PyInt_FromSsize_t(res);
}
这里Py_SIZE是抓取0的Py_SIZE(返回tp_itemsize)的宏,而tp_itemsize是从该类型抓取Py_SIZE的另一个宏。 tp_basicsize计算为sizeof(PyListObject),其中PyListObject是实例结构。
Py_SIZE结构有三个字段:
PyObject_VAR_HEAD # 24 bytes
PyObject **ob_item; # 8 bytes
Py_ssize_t allocated; # 8 bytes
这些评论(我修剪)解释它们是什么,按照上面的链接阅读它们。 Py_SIZE扩展为三个8字节字段(Py_SIZE,0和tp_itemsize),因此有tp_itemsize字节的贡献。
所以目前Py_SIZE是:
sizeof(PyListObject) + self->allocated * sizeof(void*)
要么:
40 + self->allocated * sizeof(void*)
如果列表实例具有已分配的元素。 第二部分计算他们的贡献。 Py_SIZE,顾名思义,保存已分配元素的数量。
没有任何元素,列表的大小计算为:
>>> [].__sizeof__()
40
即实例结构的大小。
Py_SIZE对象未定义0功能。 相反,他们使用Py_SIZE来计算它们的大小:
static PyObject *
object_sizeof(PyObject *self, PyObject *args)
{
Py_ssize_t res, isize;
res = 0;
isize = self->ob_type->tp_itemsize;
if (isize > 0)
res = Py_SIZE(self) * isize;
res += self->ob_type->tp_basicsize;
return PyInt_FromSsize_t(res);
}
对于Py_SIZEs,这获取0,如果对象具有非零tp_itemsize(意味着它具有可变长度实例),则它将元组中的项目数(通过Py_SIZE获得)与tp_itemsize相乘。
Py_SIZE再次使用0,其中Py_SIZE结构包含:
PyObject_VAR_HEAD # 24 bytes
PyObject *ob_item[1]; # 8 bytes
因此,没有任何元素(即Py_SIZE返回0),空元组的大小等于list:
>>> ().__sizeof__()
24
是吧? 好吧,这是一个奇怪的我没有找到解释,list的tp_basicsizes实际计算如下:
sizeof(PyTupleObject) - sizeof(PyObject *)
为什么从tp_basicsize中删除了额外的list字节是我无法找到的。 (有关可能的解释,请参阅MSeifert的评论)
但是,这基本上是您具体示例的不同之处。 lists还保留了许多已分配的元素,这有助于确定何时再次进行过度分配。
现在,当添加其他元素时,列表确实会执行此过度分配以实现O(1)追加。 这导致更大的尺寸,因为MSeifert在他的回答中很好地涵盖了。