列表切片赋值给另一个变量,浅拷贝原理解析

把一个列表的切片赋值给另一个变量,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 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!