我假设您使用的是 64 位 CPython(我在 CPython 2.7 64 位上得到了相同的结果)。其他 Python 实现或者如果您有 32 位 Python,则可能存在差异。
无论实施情况如何,list
s 大小可变,而tuple
s 是固定大小的。
So tuple
s 可以直接在结构内部存储元素,另一方面,列表需要一个间接层(它存储指向元素的指针)。这一层间接是一个指针,在 64 位系统上是 64 位,因此是 8 个字节。
但还有一件事是list
s do:他们过度分配。否则list.append
将是一个O(n)
手术always- 使其摊销O(1)
(快得多!!!)它过度分配。但现在它必须跟踪已分配尺寸和filled size (tuple
s 只需要存储一种大小,因为分配的大小和填充的大小始终相同)。这意味着每个列表必须存储另一个“大小”,在 64 位系统上是一个 64 位整数,同样是 8 个字节。
So list
s 需要至少多 16 个字节的内存tuple
s。为什么我说“至少”?因为超额分配。过度分配意味着分配了超出需要的空间。但是,过度分配的数量取决于您创建列表和追加/删除历史记录的“方式”:
>>> l = [1,2,3]
>>> l.__sizeof__()
64
>>> l.append(4) # triggers re-allocation (with over-allocation), because the original list is full
>>> l.__sizeof__()
96
>>> l = []
>>> l.__sizeof__()
40
>>> l.append(1) # re-allocation with over-allocation
>>> l.__sizeof__()
72
>>> l.append(2) # no re-alloc
>>> l.append(3) # no re-alloc
>>> l.__sizeof__()
72
>>> l.append(4) # still has room, so no over-allocation needed (yet)
>>> l.__sizeof__()
72
Images
我决定创建一些图像来配合上面的解释。也许这些有帮助
在您的示例中,这就是它(示意性地)存储在内存中的方式。我强调了与红色(徒手)循环的差异:
这实际上只是一个近似值,因为int
对象也是 Python 对象,CPython 甚至重用小整数,因此内存中对象的更准确表示(尽管不那么可读)可能是:
有用的链接:
- tuplePython 2.7 的 CPython 存储库中的结构 https://github.com/python/cpython/blob/2.7/Include/tupleobject.h#L24-L32
- listPython 2.7 的 CPython 存储库中的结构 https://github.com/python/cpython/blob/2.7/Include/listobject.h#L22_L39
- intPython 2.7 的 CPython 存储库中的结构 https://github.com/python/cpython/blob/2.7/Include/intobject.h#L23-L26
注意__sizeof__
并没有真正返回“正确”的尺寸!它仅返回存储值的大小。但是当你使用sys.getsizeof https://docs.python.org/2/library/sys.html#sys.getsizeof结果不同:
>>> import sys
>>> l = [1,2,3]
>>> t = (1, 2, 3)
>>> sys.getsizeof(l)
88
>>> sys.getsizeof(t)
72
有 24 个“额外”字节。这些都是real,这是未计入的垃圾收集器开销__sizeof__
方法。这是因为您通常不应该直接使用魔术方法 - 使用知道如何处理它们的函数,在这种情况下:sys.getsizeof https://github.com/python/cpython/blob/2.7/Python/sysmodule.c#L687-L761(实际上增加 GC 开销 https://github.com/python/cpython/blob/2.7/Python/sysmodule.c#L731-L732返回值__sizeof__
).