I will take a deeper dive into the CPython code base so that we can see how the sizes are calculated. In your specific example, the redistributions were not performed, so I will not touch on this.
I will use 64-bit values ββhere, like you.
The size for list calculated by the following function: 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); }
Here Py_TYPE(self) is a macro that captures ob_type of self (returns PyList_Type ), and _PyObject_SIZE is another macro that captures tp_basicsize from this type. tp_basicsize evaluates to sizeof(PyListObject) , where PyListObject is the instance structure.
The PyListObject structure has three fields:
PyObject_VAR_HEAD # 24 bytes PyObject **ob_item; # 8 bytes Py_ssize_t allocated; # 8 bytes
They have comments (which I cut back) explaining what they are, following the link above to read them. PyObject_VAR_HEAD expands to three 8 byte fields ( ob_refcount , ob_type and ob_size ), so the tab is 24 byte.
So now res :
sizeof(PyListObject) + self->allocated * sizeof(void*)
or
40 + self->allocated * sizeof(void*)
If the list instance contains items that are highlighted. the second part calculates their contribution. self->allocated , as the name implies, contains the number of selected elements.
Without any elements, the size of the lists is calculated as:
>>> [].__sizeof__() 40
ie the size of the instance structure.
The objects
tuple do not define the tuple_sizeof function. Instead, they use object_sizeof to calculate their size:
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); }
This, as for list s, captures tp_basicsize , and if the object has a non-zero tp_itemsize (this means that it has instances of variable length), it multiplies the number of elements in the tuple (which it gets through Py_SIZE ) with tp_itemsize .
tp_basicsize uses sizeof(PyTupleObject) again, where PyTupleObject struct contains :
PyObject_VAR_HEAD # 24 bytes PyObject *ob_item[1]; # 8 bytes
So, without any elements (i.e. Py_SIZE returns 0 ), the size of the empty tuples is equal to sizeof(PyTupleObject) :
>>> ().__sizeof__() 24
but? Well, here is a weirdness that I have not found an explanation, tp_basicsize of tuple actually calculated as follows:
sizeof(PyTupleObject) - sizeof(PyObject *)
why the extra 8 bytes are removed from tp_basicsize is something that I could not find out. (See MSeifert Comment for a possible explanation)
But this is basically the difference in your specific example. list also supports a number of highlighted items that help determine when to reassign again.
Now that additional items are being added, lists really do this over-allocation to achieve the addition of O (1). This leads to a larger size, since MSeifert describes his answer well.