平時寫python用慣了numpy的矩陣型別,只用python自帶的list做有關矩陣的(二維陣列的)處理的時候碰到各種bug。這裡是今日份的bug和解決方案。
在乙個程式中,我們希望用list實現乙個二維陣列,然後對其中的元素挨個根據下標的指引來進行賦值。我們對這個二維陣列也就是矩陣的初始化是這樣的:
m, n =5, 3
matrix = [[1] * n] * m
其中m,n分別是行數和列數。乍一看沒有什麼問題,但是在賦值的時候出現了這樣的一幕:
matrix
out[199]: [[1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1]]
matrix[1][0] = 233
matrix
out[201]: [[233, 1, 1], [233, 1, 1], [233, 1, 1], [233, 1, 1], [233, 1, 1]]
???
我們發現,雖然用的是二維陣列的下標,結果不僅僅我們的第1行第0列被賦值,而且所有其他行的第0列也跟著賦值了。這是什麼原因呢?
問題就處在我們的初始化的方法上。
在python 中,對乙個list後面用 乘號 再加上 數字 的方法來初始化乙個list,實際上是對這個list進行了淺拷貝(shallow copy),在python中,有深拷貝(deep copy)和 淺拷貝 的區別。簡單來講就是:深拷貝就是把要拷貝的物件整體複製乙份,存在新開闢的空間裡;而淺拷貝指的是,對於要拷貝的物件要複製乙份,但是對於其內部的子物件就不複製了,而是直接引用,也就是類似於新增了乙個鏈結而已,如下:
import copy
aout[210]: [888, 2, 3, [4, 5]]
b = copy.copy(a)
bout[212]: [888, 2, 3, [4, 5]]
b[0] = 233
bout[214]: [233, 2, 3, [4, 5]]
aout[215]: [888, 2, 3, [4, 5]]
b[3][1] = 666
bout[217]: [233, 2, 3, [4, 666]]
aout[218]: [888, 2, 3, [4, 666]]
這裡就很明顯了,我們對a做乙個淺拷貝,目的是b,然後我們對b進行操作,如果對list中的整數賦值,也就是物件中的元素賦值,那麼就只改變b中的這個位置的元素值,而a的不變;但是如果我們對list中的list,也就是子物件進行賦值,那麼我們法線,這個操作同樣也影響了a中的結果。
copy 這個模組裡的copy()是淺拷貝的函式,我們在試一下深拷貝deepcopy()函式:
a
out[219]: [888, 2, 3, [4, 666]]
b = copy.deepcopy(a)
bout[221]: [888, 2, 3, [4, 666]]
b[0] = 1
bout[223]: [1, 2, 3, [4, 666]]
aout[224]: [888, 2, 3, [4, 666]]
b[3][0] = 233
bout[226]: [1, 2, 3, [233, 666]]
aout[227]: [888, 2, 3, [4, 666]]
這就很科學了!兩個物件 a 和 b 互不打擾,很和諧。
另外,我們通常的賦值操作(list的直接賦值)又是怎樣的結果呢?
a
out[229]: [888, 2, 3, [4, 666]]
b = a
bout[231]: [888, 2, 3, [4, 666]]
b[0] = 1
bout[233]: [1, 2, 3, [4, 666]]
aout[234]: [1, 2, 3, [4, 666]]
b[3][0] = 1
bout[236]: [1, 2, 3, [1, 666]]
aout[237]: [1, 2, 3, [1, 666]]
可以看出來,直接賦值的結果是比淺拷貝還淺拷貝的。。。因為a和b根本上就是指向的同乙個物件!也就是說,a和b是這個物件的兩個引用(reference),修改乙個就會改變另乙個。
了解了這兩個概念的區別,就可以解釋上述的問題。首先,我們對
[1] * 3
# 建立了 1 的三個淺拷貝,得到了[1,1,1],此時我們修改某個1,不會影響其他的,因為這是int的元素,不是子物件
[[1] * 3] * 4 建立了list列表[1,1,1]的四個淺拷貝,這裡得到的list是以list為元素型別的,因此改變乙個就會影響其他
# 這就是我們開始會得到所有行都改變的原因,因為他們實際上指向的是同乙個東西!
改用如下方式初始化陣列,就可以得到乙個可以通過二維下標訪問的矩陣了。
matrix = [[ 1
for i in
range(n)] for i in
range(m)]
這就是之前整理過的list comprehension的方法生成list。
(貌似這篇文章裡有好多感嘆號。。。因為學藝不精。。。被這個bug折騰了半天才發現它。。。好氣。。。)
2023年03月30日20:07:40
python中的淺拷貝和深拷貝
不得不說 python核心程式設計 是一本好書,看到其中一節做一下隨筆。在python中,當建立乙個物件後,然後把它賦給另乙個物件時,python並沒有去拷貝這個物件,而是拷貝了這個物件的引用。看不懂沒關係,我們看乙個例子。raw list first second 12 32 copy list ...
python中的深拷貝和淺拷貝
淺拷貝 copy 不拷貝物件的內容,僅僅拷貝子物件的引用 深拷貝 deepcopy 會連同拷貝子物件的記憶體,對子物件的修改不會影響源物件 下面用 來測試 import copy deftest copy 淺拷貝測試 a 10 20,5,6 b copy.copy a print a a,end t...
python中的淺拷貝和深拷貝
本篇介紹下python中的深拷貝和淺拷貝,主要從基本型別 類 不可變型別等方面進行介紹。1.介紹拷貝之前首先應該明白is和 的區別,即is表示同乙個物件,比較的是值 a 1000 b 1000 a b true a is bfalse class person object def init sel...