我将深入研究 CPython 代码库,以便了解实际计算大小的方式。 在您的具体示例中,没有执行过度分配,所以我不会涉及到这一点。
我将在这里使用 64 位值,就像你一样。
lists 的大小由以下函数计算得出,list_sizeof:
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_TYPE(self) 是一个宏,它获取self 的ob_type(返回PyList_Type),而_PyObject_SIZE 是另一个从该类型中获取tp_basicsize 的宏。 tp_basicsize 计算为sizeof(PyListObject),其中PyListObject 是实例结构。
PyListObject structure 具有三个字段:
PyObject_VAR_HEAD # 24 bytes
PyObject **ob_item; # 8 bytes
Py_ssize_t allocated; # 8 bytes
这些有 cmets(我修剪过)解释它们是什么,请按照上面的链接阅读它们。 PyObject_VAR_HEAD 扩展为三个 8 字节字段(ob_refcount、ob_type 和 ob_size),因此 24 字节贡献。
所以现在res 是:
sizeof(PyListObject) + self->allocated * sizeof(void*)
或:
40 + self->allocated * sizeof(void*)
如果列表实例具有已分配的元素。第二部分计算他们的贡献。 self->allocated,顾名思义,保存已分配元素的数量。
没有任何元素,列表的大小计算为:
>>> [].__sizeof__()
40
即实例结构的大小。
tuple 对象没有定义tuple_sizeof 函数。相反,他们使用object_sizeof 来计算它们的大小:
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);
}
对于lists,这会获取tp_basicsize,如果对象有一个非零tp_itemsize(意味着它有可变长度的实例),它将乘以元组中的项目数(它通过Py_SIZE) 和tp_itemsize 获得。
tp_basicsize 再次使用sizeof(PyTupleObject),其中PyTupleObject struct contains:
PyObject_VAR_HEAD # 24 bytes
PyObject *ob_item[1]; # 8 bytes
所以,没有任何元素(即Py_SIZE 返回0),空元组的大小等于sizeof(PyTupleObject):
>>> ().__sizeof__()
24
嗯?好吧,这是一个我没有找到解释的奇怪之处,tuples 的 tp_basicsize 实际上是这样计算的:
sizeof(PyTupleObject) - sizeof(PyObject *)
为什么从tp_basicsize 中删除了额外的8 字节是我无法找到的。 (有关可能的解释,请参阅 MSeifert 的评论)
但是,这基本上是您具体示例中的区别。 lists 还保留了一些已分配的元素,这有助于确定何时再次过度分配。
现在,当添加其他元素时,列表确实会执行这种过度分配以实现 O(1) 附加。这导致更大的尺寸,因为 MSeifert 在他的回答中很好地涵盖了。