Python中元组
占用的内存空间较小:
>>> a = (1,2,3)
>>> a.__sizeof__()
48
而 list
占用更多的内存空间:
>>> b = [1,2,3]
>>> b.__sizeof__()
64
Python的内存管理是如何进行的?
Python中元组
占用的内存空间较小:
>>> a = (1,2,3)
>>> a.__sizeof__()
48
而 list
占用更多的内存空间:
>>> b = [1,2,3]
>>> b.__sizeof__()
64
>>> 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
我决定创建一些图像来配合上面的解释。也许这些对您有所帮助。
这是如何(示意性地)在内存中存储您的示例的。我用红色(手绘)循环突出显示了区别:
那实际上只是一个近似值,因为int
对象也是Python对象,而CPython甚至重用小整数,因此对象在内存中的更准确表示(虽然不太可读)可能是:
有用的链接:
请注意,__sizeof__
实际上并没有返回“正确”的大小!它只返回存储值的大小。但是当您使用sys.getsizeof
时,结果会有所不同:
>>> import sys
>>> l = [1,2,3]
>>> t = (1, 2, 3)
>>> sys.getsizeof(l)
88
>>> sys.getsizeof(t)
72
sys.getsizeof
(实际上将GC开销添加到从`__sizeof__`返回的值中)。list()
或列表推导式时超分配的数量,这是有用的。 - MSeifert对于list
的大小是通过以下函数计算的,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
结构体有三个字段:
PyObject_VAR_HEAD # 24 bytes
PyObject **ob_item; # 8 bytes
Py_ssize_t allocated; # 8 bytes
这些都有注释(我已经修剪了)解释它们是什么,请点击上面的链接阅读。PyObject_VAR_HEAD
展开为三个 8 字节字段(ob_refcount
、ob_type
和 ob_size
),因此贡献了 24
字节。
因此,现在的 res
是:
sizeof(PyListObject) + self->allocated * sizeof(void*)
或者:
40 + self->allocated * sizeof(void*)
>>> [].__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);
}
tp_basicsize
,如果对象具有非零tp_itemsize
(表示它具有可变长度实例),则会将元组中项目的数量(通过Py_SIZE
获得)乘以tp_itemsize
。 tp_basicsize
再次使用sizeof(PyTupleObject)
,其中PyTupleObject
结构包含:PyObject_VAR_HEAD # 24 bytes
PyObject *ob_item[1]; # 8 bytes
因此,没有任何元素(即Py_SIZE
返回0
),空元组的大小等于sizeof(PyTupleObject)
:
>>> ().__sizeof__()
24
咦?好吧,这里有一个我没有找到解释的奇怪现象,tuple
的 tp_basicsize
实际上是按照以下方式计算的:
sizeof(PyTupleObject) - sizeof(PyObject *)
ob_item [1]
大多是一个占位符(因此从basicsize中减去它是有意义的)。tuple
是使用PyObject_NewVar
分配的。我还没有弄清楚细节,所以这只是一个有根据的猜测... - MSeifertMSeifert的回答涵盖了广泛内容,简单来说可以这样理解:
元组
是不可变的。一旦设定,就无法更改它。因此,您可以预先知道需要为该对象分配多少内存。
列表
是可变的。您可以向其中添加或删除项目。它必须知道其当前大小。随着需要,它会调整大小。
“天下没有白吃的午餐”- 这些功能伴随着代价。因此,在内存中使用列表会增加开销。
元组的大小是固定的,即在初始化时解释器为其中包含的数据分配足够的空间,因此它是不可变的(无法修改)。而列表是可变对象,因此意味着动态分配内存,因此为了避免每次追加或修改列表时都要分配空间(分配足够的空间来包含更改后的数据并将数据复制到其中),它会为未来的运行时更改(如追加和修改)分配额外的空间。
这基本上就是总结了。