Python 列表的切片和賦值操作很基礎,之前也遇到過一些坑,以為自己很懂了。但今天刷 Codewars 時發現了一個更大的坑,故在此記錄。
Python 列表賦值:複製“值”還是“引用”?
很多入門 Python 的人會犯這樣一個錯誤:在賦值操作=
中搞不清是賦了“值”還是“引用”。比如:
a = [1, 2, 3]
b = a
b[0] = 10 # 更改列表 b 的第一個元素,但 a 現在也被更改為了 [10, 2, 3]
他可能只想改變列表b
,但實際上這樣也會改變列表a
。
因為b
實際上是列表a
的另一個引用,a
和b
是同一個物件,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)
為False
,a
和b
確實不是同一個物件。但它們的元素都是同一個物件——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
。