Python - 元组和内存管理

Python - tuples and memory management

在回答 Python 相关问题时,我做了一些实验,发现了一个我无法弄清楚的与元组相关的问题:我很难理解为什么空元组需要更多 space然后是一个包含单个元素的元组(根据 sys.getsizeof())。下面的代码是在 64 位 Debian Jessie 系统上使用 Python 2.7.9 的上游版本执行的(还使用 Python 3.4.2 进行了测试,其中值略有不同,但我的整体行为我说的还在:

>> sys.getsizeof(())
56
>> sys.getsizeof((1))
24
>> sys.getsizeof((1,2))
72

如您所见,空元组与单项元组之间存在很大差异(更准确地说是 2.3 倍)。知道这里发生了什么吗?由于元组是递归数据结构,我假设 sys.getsizeof() returns 的值是元组对象本身占用的内存量加上对它包含的对象的引用(如果元组将元素存储为引用而不是作为值-我不知道)。我最初的想法是,像字典一样,元组是在内存中创建的,具有一定的默认大小,可以容纳几个元素。我忘记了空字典保留内存的元素的确切数量,但这是我用一个小例子的意思:

>> sys.getsizeof({})
280
>> sys.getsizeof({"a":0})
280
>> sys.getsizeof({"a":0, "b":1})
280

然而,查看元组似乎并没有表现出相同的行为,因为元组在第一个元素添加到较小尺寸后收缩,然后随着每个元素添加到它而增长(如预期)。除此之外,列表似乎没有出现相同的行为:

>> sys.getsizeof([])
72
>> sys.getsizeof([1])
80
>> sys.getsizeof([1,2])
88

内存方面的空列表比具有 1 个或多个元素的列表小 - 完全正常。

我的第二个想法是,单元素元组以某种方式转换为它包含的单个对象,所有这些都以某种方式包装,因此看起来它实际上是一个列表(这就是 len() 起作用的原因).示例:

>> sys.getsizeof((1))
24
>> sys.getsizeof(1)
24

这似乎是可能的,但我不相信它真的发生了。

代码(1)不是元组! Python 只会将括号视为表示操作优先级的一种方式。如果你想要一个元组,它必须至少有一个逗号。

>>> type(1)
<type 'int'>
>>> type((1))
<type 'int'>
>>> type((1,))
<type 'tuple'>

那么元组的大小将是其中元素数量的线性函数。

>>> sys.getsizeof(tuple())
28
>>> sys.getsizeof((1,))
32
>>> sys.getsizeof((1,2))
36
>>> sys.getsizeof((1,2,3))
40

您没有创建单项元组。任何表达式都可以包含在括号中而不改变其含义,因此 (1)1 相同。要创建单个元素元组,请编写 (1,)。当你这样做时,内存消耗应该会变得更合理。

But how can an integer require 24 bytes? Aren't integers usually 8 bytes at most?

在Python3中,所有整数都是任意精度的,即使在Python2中,包括整数在内的所有对象对于引用计数、类型信息和其他元数据都有一定的开销。 24 个字节对于一个完整的 Python 对象来说实际上是相当合理的。

(从技术上讲,小整数是驻留的,因此在这种特殊情况下可以说引用计数是不必要的,但删除它会使解释器的其他部分复杂化,没有任何显着好处。)

在回答的同时,我还想澄清一些事情:

(if tuples store elements as references and not as values - that I don't know)

Python,一般来说,不允许Python对象实际包含彼此。在 C 源代码中,您会看到很多 PyObject* 个变量,但没有 PyObject 个变量。 Python 对象存在于堆中,彼此完全独立。换句话说,是的,元组存储的是引用,而不是实际的对象。

My initial thought was that like dictionaries tuples are created in memory with a certain default size that can hold a couple of elements.

没有理由这样做。元组有固定的大小。正式地,它们是不可变的,并且在创建后不会改变。非正式地,它们可以在 C 级别进行更改,但这些更改不应该对 Python 代码可见;您在创建元组之后但在将其用于任何事情之前修改一次一次,此后您不会再次修改它。此外,这些更改实际上并没有改变大小,它们只是填充已经存在的数组槽。

由于这种不变性,元组没有理由存储比实际更多的元素。它们不需要支持快速附加或其他大小更改操作。