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。
