把一個列表的切片賦值給另一個變數,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;
}