Python切片赋值机制深度解析:从源码看PyList_SetSlice
本文通过Python 2.7.6源码深入解析切片赋值机制。重点分析Objects/listobject.c中PyList_SetSlice与list_ass_slice函数的实现逻辑,解释如t[1:1]=[7]这类操作的底层行为。通过源码注释与关键代码段,阐明列表内存重分配、元素移动及引用计数管理的内部过程,帮助开发者理解切片赋值的实际效果与潜在性能影响。
本文重点探讨Python中切片赋值的底层原理,旨在为读者提供深入的技术参考。下文将详细展开相关机制的分析与解读。
日前,有开发者提出一个具体的代码疑问:
t = [1, 2, 3] t[1:1] = [7] # 感谢@一往直前 的疑问,之前写为 t[1:1] = 7了 print t # 输出 [1, 7, 2, 3]
此前确实较少见到针对列表进行此类切片赋值的场景。然而,该操作产生的输出结果背后的原因值得深入探究,这有助于理解Python内部机制,毕竟此前已阅读过《Python源码分析》相关文献(补充说明:据悉近期有专家正在开发新版本)。
趁着今日有空,决定查阅Python源码,以厘清其背后的实现原理。
注:本地环境中下载的是Python 2.7.6版本的源代码,以下分析基于该版本进行。
在Objects/listobject.c文件中,存在一个名为PyList_SetSlice的函数,其定义如下:
int
PyList_SetSlice(PyObject *a, Py_ssize_t ilow, Py_ssize_t ihigh, PyObject *v)
{
if (!PyList_Check(a)) {
PyErr_BadInternalCall();
return -1;
}
return list_ass_slice((PyListObject *)a, ilow, ihigh, v);
}
其中关键调用涉及list_ass_slice,接下来我们将深入查看该函数的具体代码实现:
static int
list_ass_slice(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh, PyObject *v)
{
/* Because [X]DECREF can recursively invoke list operations on
this list, we must postpone all [X]DECREF activity until
after the list is back in its canonical shape. Therefore
we must allocate an additional array, 'recycle', into which
we temporarily copy the items that are deleted from the
list. :-( */
PyObject *recycle_on_stack[8];
PyObject **recycle = recycle_on_stack; /* will allocate more if needed */
PyObject **item;
PyObject **vitem = NULL;
PyObject *v_as_SF = NULL; /* PySequence_Fast(v) */
Py_ssize_t n; /* # of elements in replacement list */
Py_ssize_t norig; /* # of elements in list getting replaced */
Py_ssize_t d; /* Change in size */
Py_ssize_t k;
size_t s;
int result = -1; /* guilty until proved innocent */
#define b ((PyListObject *)v)
if (v == NULL)
n = 0;
else {
if (a == b) {
/* Special case "a[i:j] = a" -- copy b first */
v = list_slice(b, 0, Py_SIZE(b));
if (v == NULL)
return result;
result = list_ass_slice(a, ilow, ihigh, v);
Py_DECREF(v);
return result;
}
v_as_SF = PySequence_Fast(v, "can only assign an iterable");
if(v_as_SF == NULL)
goto Error;
/*
the5fire注:
要赋值的长度n
*/
n = PySequence_Fast_GET_SIZE(v_as_SF);
vitem = PySequence_Fast_ITEMS(v_as_SF);
}
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);
norig = ihigh - ilow;
assert(norig >= 0);
d = n - norig;
if (Py_SIZE(a) + d == 0) {
Py_XDECREF(v_as_SF);
return list_clear(a);
}
item = a->ob_item;
/* recycle the items that we are about to remove */
s = norig * sizeof(PyObject *);
if (s > sizeof(recycle_on_stack)) {
recycle = (PyObject **)PyMem_MALLOC(s);
if (recycle == NULL) {
PyErr_NoMemory();
goto Error;
}
}
memcpy(recycle, &item[ilow], s);
if (d < 0) { /* Delete -d items */
memmove(&item[ihigh+d], &item[ihigh],
(Py_SIZE(a) - ihigh)*sizeof(PyObject *));
list_resize(a, Py_SIZE(a) + d);
item = a->ob_item;
}
else if (d > 0) { /* Insert d items */
k = Py_SIZE(a);
if (list_resize(a, k+d) < 0)
goto Error;
item = a->ob_item;
printf("关键点\n");
/*
the5fire注:
把list对应切片后一位的值之后的所有内容向后移动所赋值的大小
按照上面的python代码这里就是
原理的t:
|1|2|3|
后移一位,因为len([7]) = 1
|1|空|2|3|把后两个移位
*/
memmove(&item[ihigh+d], &item[ihigh],
(k - ihigh)*sizeof(PyObject *));
}
/*
the5fire注:
赋值操作,即把[7]赋值到t里的对应位置上
ilow是1, n是1
*/
for (k = 0; k < n; k++, ilow++) {
PyObject *w = vitem[k];
Py_XINCREF(w);
item[ilow] = w;
}
for (k = norig - 1; k >= 0; --k)
Py_XDECREF(recycle[k]);
result = 0;
Error:
if (recycle != recycle_on_stack)
PyMem_FREE(recycle);
Py_XDECREF(v_as_SF);
return result;
#undef b
}
参考知乎与StackOverflow上的相关解答后,发现查阅源码是最为直接的解释方式。上述关键位置已添加注释,便于理解其逻辑流程。
总结
以上为本文的全部内容,希望这些分析能对各位的学习或工作有所助益。若有任何疑问,欢迎留言交流,感谢对本站的支持。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













