Below is a pop operation of Python written in C.
static PyObject *
list_pop_impl(PyListObject *self, Py_ssize_t index)
/*[clinic end generated code: output=6bd69dcb3f17eca8 input=b83675976f329e6f]*/
{
PyObject *v;
int status;
if (Py_SIZE(self) == 0) {
/* Special-case most common failure cause */
PyErr_SetString(PyExc_IndexError, "pop from empty list");
return NULL;
}
if (index < 0)
index += Py_SIZE(self);
if (!valid_index(index, Py_SIZE(self))) {
PyErr_SetString(PyExc_IndexError, "pop index out of range");
return NULL;
}
v = self->ob_item[index];
if (index == Py_SIZE(self) - 1) {
status = list_resize(self, Py_SIZE(self) - 1);
if (status >= 0)
return v; /* and v now owns the reference the list had */
else
return NULL;
}
Py_INCREF(v);
status = list_ass_slice(self, index, index+1, (PyObject *)NULL);
if (status < 0) {
Py_DECREF(v);
return NULL;
}
return v;
}
I understand that pop(index) takes O(N) time. But there’s no FOR statement here.
It just returns v.
If pop (0), does Python move all the pointers inside the list forward one by one?
>Solution :
Recall that at the C level, a list is an array of pointers to the objects contained in the list.
In the code above, it calls list_ass_slice() which literally just calls memmove() to relocate the pointers after the popped element.
From Objects/listobject.c:list_ass_slice():
if (d < 0) { /* Delete -d items */
Py_ssize_t tail;
tail = (Py_SIZE(a) - ihigh) * sizeof(PyObject *);
memmove(&item[ihigh+d], &item[ihigh], tail);
The O(N) is a reflection of the time it takes to relocate (up to) N objects of sizeof(PyObject *) bytes.