列表切片赋值给另一个变量,浅拷贝原理解析
把一个列表的切片赋值给另一个变量,Python内部发生了什么?
如下所示,创建列表a,然后把它的一段切片赋值给b:
>>>
>>> a = [2, "Hello", 5, [3,4,7],{1:2}] # 创建列表a
>>>
>>> a
[2, 'Hello', 5, [3, 4, 7], {1: 2}]
>>>
>>>
>>> a[2:5] # 访问a的切片
[5, [3, 4, 7], {1: 2}]
>>>
>>>
>>> b = a[2:5] # 把a切片赋值给b
>>>
>>> b
[5, [3, 4, 7], {1: 2}]
>>>
列表在C语言当中是一个PyListObject结构体,这个结构体当中有一个成员ob_item,它是一个指针,指向一块内存,而这块内存存储了一系列指针,每一个指针分别指向列表的每一个成员。如下图所示:
在访问a的切片时,会新创建一个列表对象,也就是PyListObject结构体,并为它的ob_item指针分配一块内存,然后把a中的ob_item所指向的,切片所需的内容拷贝过去。
把切片赋值给b,实际上就是让b指向了这一个新创建的列表对象。
由于ob_item所指向的内存所存储的是一系列指针,拷贝时仅拷贝了这些指针,所以b和部分a成员实际上指向相同的对象,这些对象并没有重新创建一份,这也就是所谓的浅拷贝。下图所示红色部分就是拷贝的内容。
C代码:
把切片赋值给b时,调用了如下所示函数创建新列表,并根据传入的参数把对应的切片内容拷贝到新列表当中。
static PyObject *
list_slice(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh)
{
PyListObject *np;
PyObject **src, **dest;
Py_ssize_t i, len;
len = ihigh - ilow;
np = (PyListObject *) list_new_prealloc(len); // 申请了一块内存用来存储新列表
if (np == NULL)
return NULL;
src = a->ob_item + ilow;
dest = np->ob_item;
for (i = 0; i < len; i++) { // 将列表a的ob_item中对应的值复制到新列表中
PyObject *v = src[i];
Py_INCREF(v);
dest[i] = v;
}
Py_SIZE(np) = len;
return (PyObject *)np;
}
上面代码中的申请内存的函数如下所示:
static PyObject *
list_new_prealloc(Py_ssize_t size)
{
PyListObject *op = (PyListObject *) PyList_New(0); // 参数0是指PyListObject中的ob_item成员不申请内存
if (size == 0 || op == NULL) { // 它在下面代码中单独申请
return (PyObject *) op;
}
assert(op->ob_item == NULL);
op->ob_item = PyMem_New(PyObject *, size); // 为ob_item申请内存
if (op->ob_item == NULL) {
Py_DECREF(op);
return PyErr_NoMemory();
}
op->allocated = size;
return (PyObject *) op;
}
本作品采用《CC 协议》,转载必须注明作者和本文链接