Python 列表切片陷阱:引用、複製與深複製

NaN不等於NaN發表於2019-02-14

Python 列表的切片和賦值操作很基礎,之前也遇到過一些坑,以為自己很懂了。但今天刷 Codewars 時發現了一個更大的坑,故在此記錄。

Python 列表賦值:複製“值”還是“引用”?

很多入門 Python 的人會犯這樣一個錯誤:在賦值操作=中搞不清是賦了“值”還是“引用”。比如:

a = [1, 2, 3]
b = a
b[0] = 10  # 更改列表 b 的第一個元素,但 a 現在也被更改為了 [10, 2, 3]

他可能只想改變列表b,但實際上這樣也會改變列表a

因為b實際上是列表a的另一個引用ab是同一個物件,id(a) == id(b),所以更改b也會更改a。這個應該大部分人都知道。所以正確的程式碼應該使用切片來進行列表的複製

a = [1, 2, 3]
b = a[:]  # 使用切片進行列表複製
b[0] = 10  # 此時 a 和 b 是兩個不同的物件

二維列表引發的思考:列表的本質

好的,現在我們確定切片能夠進行列表的複製。那我們就能心安理得地改動新的列表了嗎?請看二維列表(二維陣列):

a = [[1, 2, 3], [4, 5, 6]]
b = a[:]
b[0][0] = 10

此時,a還是被改動了!

原因是,雖然id(a) == id(b)Falseab確實不是同一個物件。但它們的元素都是同一個物件——id(a[0]) == id(b[0])id(a[1]) == id(b[1])。因為列表裡儲存的是物件的引用!

列表 list 終究只是個容器。就像 tuple 本身是 immutable (不可變)的,但它只是容器,它可以儲存一個可變物件,因此呈現出一種可以被改動的“假象”。例如:

>>> a = ([1],)
>>> a[0][0] = 2
>>> a
([2],)

所以容器和它儲存的物件不能混為一談。所以對於這種二維列表,想要進行完全的複製,請直接使用copy.deepcopy()深度複製。

如果只想複製一部分(切片),那可以先複製再切片:

>>> import copy
>>> a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> b = copy.deepcopy(a)[1:]
>>> b[0][0] = 100

>>> a
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> b
[[100, 5, 6], [7, 8, 9]]

此時修改b沒有影響到a

相關文章