__sizeof__ 字符串比包含该字符串的元组的 __sizeof__ 更大

5
以下代码生成了给定的输出。
import sys

print('ex1:')
ex1 = 'Hello'
print('\t', ex1.__sizeof__())

print('\nex2:')
ex2 = ('Hello', 53)
print('\t', ex2.__sizeof__())

输出:

ex1:
     54    
ex2:
     40

为什么在考虑第二个元素时,__sizeof__() 输出的结果更小?输出不应该更大吗?我了解到从这个答案中得知应该使用 sys.getsizeof(),但行为似乎仍然很奇怪。我正在使用Python 3.5.2
另外,正如@Herbert所指出的,'Hello' 占用的内存比 ('Hello',) 即一个包含单个元素的tuple 要多。为什么会这样呢?

你的第一个对象不是元组,而是括号内的字符串。 - Mazdak
1个回答

14
这是因为tuple对象(我相信除了字符串之外的所有容器)评估它们的大小不是通过包括其各自内容的实际大小,而是通过计算指向PyObject的指针的大小乘以它们包含的元素来计算。也就是说,它们保存对所包含的(通用)PyObject的指针,这就是其整体大小的贡献。
在Python参考手册的数据模型章节中提到了这一点:

有些对象包含对其他对象的引用;这些被称为容器。元组、列表和字典都是容器的例子。这些引用是容器值的一部分。

(我强调了“引用”这个词。)

PyTupleType, 这是一个包含有关tuple类型信息的结构体,我们可以看到tp_itemsize字段的值为sizeof(PyObject *)

PyTypeObject PyTuple_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "tuple",
    sizeof(PyTupleObject) - sizeof(PyObject *),
    sizeof(PyObject *),  // <-- sizeof pointer to PyObject's

32位和64位的Python版本中,PyObject *的大小均为8个字节。

这个值将会乘以包含在tuple实例中的项目数。当我们查看object_size时,可以看到tuple继承自object__sizeof__方法(检查object.__sizeof__ is tuple.__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;  // <-- num_elements * tp_itemsize
    res += self->ob_type->tp_basicsize;

    return PyLong_FromSsize_t(res);
}

请看如何将 isize(从tp_itemsize获得)乘以Py_SIZE(self),这是另一个宏,它获取元组元素数量表示的ob_size值。

这就是为什么即使我们在元组实例中创建了一个相当大的字符串:

t = ("Hello" * 2 ** 10,)

具有大小为的元素:

t[0].__sizeof__()         # 5169

元组实例的大小:
t.__sizeof__()            # 32

等同于只有"Hello"的一个。

t2 = ("Hello",)
t[0].__sizeof__()         # 54
t2.__sizeof__()           # 32 Tuple size stays the same.

对于字符串,每个单独的字符都会增加从str.__sizeof__返回的值。这一点,加上tuple仅存储指针的事实,给人一种误导性的印象,即"Hello"比包含它的元组具有更大的大小。
仅为完整起见,unicode__sizeof__是计算此值的函数。它真正只是将字符串的长度乘以字符大小(取决于字符是12还是4字节字符)。
唯一让我困惑的是元组的基本大小(由tb_basicsize表示)被列为sizeof(PyTupleObject) - sizeof(PyObject *)。这从返回的总大小中减去了8个字节;我还没有找到任何解释(至少目前还没有)。

1
另一个顶级答案,但我想知道这些Python内部问题是否忽略了cpython标签。 - Chris_Rands
我自己也曾经想过这个问题@Chris_Rands。一段时间以前,在浏览标签中的热门问题后,我意识到大多数人将“python-internals”与“CPython”的工作方式等同起来,所以我就顺应了这种情绪 :-) - Dimitris Fasarakis Hilliard

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接