简介:
上一篇文章简单了解了一下python列表的表现形式,今天来看简单了解一下对列表的一些操作。下面是在listobject.h头文件中的声明的一些方法,其中PyList_New、PyList_SetItem上一篇中简单看了,PyList_Size方法获取列表长度的也不再多说了,接下来我们简单了解剩余的方法。
1.列表读取元素
PyObject *
PyList_GetItem(PyObject *op, Py_ssize_t i)
{
if (!PyList_Check(op)) {
PyErr_BadInternalCall();
return NULL;
}
if (i < 0 || i >= Py_SIZE(op)) {
if (indexerr == NULL) {
indexerr = PyUnicode_FromString(
"list index out of range");
if (indexerr == NULL)
return NULL;
}
PyErr_SetObject(PyExc_IndexError, indexerr);
return NULL;
}
return ((PyListObject *)op) -> ob_item[i];
}
我们找到它的源代码,那些检验代码就不再看了,直接看它最后一句。上一篇中的图示中我们可以知道ob_item指向的是一个指针数组,在C语言中通过元素下标我们可以快速定位到该元素。
2.列表插入元素
python中insert方法是可以在列表任意位置插入元素的,下面我们了解一下它是如何插入元素的。我们首先解析一下其源码:
static int
ins1(PyListObject *self, Py_ssize_t where, PyObject *v)
{
Py_ssize_t i, n = Py_SIZE(self); // 首先取到当前列表元素个数
PyObject **items;
if (v == NULL) {
PyErr_BadInternalCall();
return -1;
}
if (n == PY_SSIZE_T_MAX) {
PyErr_SetString(PyExc_OverflowError,
"cannot add more objects to list");
return -1;
}
if (list_resize(self, n+1) < 0)
return -1;
if (where < 0) {
where += n;
if (where < 0)
where = 0;
}
if (where > n)
where = n;
// 到这里就把要插入的位置固定了,上面那部分应该都是可以看懂的。
items = self->ob_item;
// 下面循环是要将插入位置之后的所有元素逐位后移,所以这里时间复杂度为O(n)
for (i = n; --i >= where; )
items[i+1] = items[i];
Py_INCREF(v);
items[where] = v; // 将要插入的元素插入指定位置
return 0;
}
(这里提一嘴,列表创建时会申请一块相对大的空间避免每次添加元素都要申请一次,如果再不够用了才再次申请。)
3.列表追加元素
追加方法相交于插入方法又简单了一些,它直接在列表末尾添加一个元素。
static int
app1(PyListObject *self, PyObject *v)
{
Py_ssize_t n = PyList_GET_SIZE(self); // 这里获取到列表长度赋值给n
assert (v != NULL);
if (n == PY_SSIZE_T_MAX) {
PyErr_SetString(PyExc_OverflowError,
"cannot add more objects to list");
return -1;
}
if (list_resize(self, n+1) < 0)
return -1;
Py_INCREF(v);
PyList_SET_ITEM(self, n, v); // 直接在索引为n也就是最后的位置插入元素指针v,这里时间复杂度为O(1)
return 0;
}
我们可以看到append方法时间复杂度是小于insert方法的。
4.列表切片
python中列表切片在很多场景下还是很好用的,下面探究一下源代码中它是如何进行切片的:
/*
直接举例说明:a = [1,2,3,4,5,6],获取b = a[2:4], 求b
分析:ilow=2,ihigh=4,带入下面的代码
*/
static PyObject *
list_slice(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh)
{
PyListObject *np; // 声明一个列表指针变量用于存储列表切片
PyObject **src, **dest;
Py_ssize_t i, len;
// 下面两个分支结构主要处理极端情况
if (ilow < 0)
ilow = 0;
else if (ilow > Py_SIZE(a))
ilow = Py_SIZE(a);
if (ihigh < ilow)
ihigh = ilow;
else if (ihigh > Py_SIZE(a))
ihigh = Py_SIZE(a);
len = ihigh - ilow; // 到这里可以计算出len=2
np = (PyListObject *) PyList_New(len); // 新建一个列表
if (np == NULL)
return NULL;
src = a->ob_item + ilow; // 此时a->ob_item应指向元素3的指针地址,下面它就作为数组首地址
dest = np->ob_item;
for (i = 0; i < len; i++) {
PyObject *v = src[i];
Py_INCREF(v);
dest[i] = v;
}
// 循环结束之后就将元素3,4的指针放到np->ob_item里面了
return (PyObject *)np;
}
通过上述示例的分析,我们可以看出切片其实是新建了一个列表。
我看到此处的时候其实是有一点绕晕的节奏,这里我想给自己提个醒,还是举个例子说明一下:
既然原列表和切片的ob_item指向的是一个指针数组,里面存放都是指针为什么a变了b没有变呢?
这里其实是我思维进入了一个方向推导的误区,停下来从列表赋值PyList_SetItem方法想了一下恍然大悟,修改列表元素是要先声明变量赋值,才能将原来的值修改为这个值啊。那顺势就简单画一下它的图示:
这里我们就可以清楚的知道为什么原列表元素改变,切片元素不变的原因了。
5.列表翻转
最后我们再看一下列表翻转的源代码,这个就很简单了,就是那列表最后一个跟第一交换,倒数第二个与第二个交换,逐个交换完成。
static void
reverse_slice(PyObject **lo, PyObject **hi)
{
assert(lo && hi);
--hi;
while (lo < hi) {
PyObject *t = *lo;
*lo = *hi;
*hi = t;
++lo;
--hi;
}
}
int
PyList_Reverse(PyObject *v)
{
PyListObject *self = (PyListObject *)v;
if (v == NULL || !PyList_Check(v)) {
PyErr_BadInternalCall();
return -1;
}
if (Py_SIZE(self) > 1)
reverse_slice(self->ob_item, self->ob_item + Py_SIZE(self));
return 0;
}
今天就先看到这里了。。。