背景
在運(yùn)用Python進(jìn)行開發(fā)代碼過程中,會(huì)遇到變量復(fù)制備份的場(chǎng)景,但并沒有得到預(yù)期的結(jié)果,例如下面的例子:
![]()
lista = ['a', 'b', [1, 2, 3]]
listb = lista.copy()
lista[2].append(4)
print(lista) # ['a', 'b', [1, 2, 3, 4]]
print(listb) # ['a', 'b', [1, 2, 3, 4]]
代碼本意是將lista復(fù)制給listb做個(gè)備份,再修改liasta,但是修改后發(fā)現(xiàn)listb也一并被修改了,沒有達(dá)到備份的效果,這個(gè)是什么原因呢?
存儲(chǔ)方式
首先了解一下Python的變量在內(nèi)存中的存儲(chǔ)方式。在基本數(shù)據(jù)類型中(包括set、list(tuple, str)、dict)都是采用引用的方式。
也就是說,每個(gè)變量都存儲(chǔ)的是這個(gè)變量的地址,而不是值本身,就算更復(fù)雜的嵌套結(jié)構(gòu),也是存儲(chǔ)是每個(gè)元素的地址而已,用一幅圖來表示。
![]()
如上圖所示,用戶看到的是 lista的4個(gè)元素值,但是內(nèi)存中保存的卻是4個(gè)元素地址。
當(dāng)元素是列表時(shí),第一層保存的是列表的地址,第二層保存的是列表元素的地址,第三層才是列表的值。當(dāng)元素是字典的時(shí)候,與列表類似。
列表的增刪改
在明白了變量存儲(chǔ)方式后,繼續(xù)看下內(nèi)存下的增刪改是怎么變化的。
列表修改已有值
新增一個(gè)內(nèi)存塊,再將引用的地址修改為新內(nèi)存塊的地址。
![]()
列表新增一個(gè)值
新增一個(gè)內(nèi)存塊,新增一個(gè)地址引用。
![]()
列表整體重新賦值
刪除變量地址和引用的值,新增地址和引用值的內(nèi)存塊。
![]()
copy與deepcopy的區(qū)別
基于以上的理解,再來看兩種copy的區(qū)別就會(huì)更容易理解了,首先記住一個(gè)原則:
copy:不管多么復(fù)雜的數(shù)據(jù)結(jié)構(gòu),淺拷貝都只會(huì)copy一層。
deepcopy:將整個(gè)變量?jī)?nèi)存全部復(fù)制一遍,新變量與原變量沒有任何關(guān)系。
舉個(gè)例子來驗(yàn)證一下上面的結(jié)論:有如下的一段代碼,最終的4個(gè)列表值是多少?
注意:引用deepcopy需要導(dǎo)入copy庫。
import copy
a = [1, 2, 3, 4, ['a', 'b']]
b = a
c = copy.copy(a)
d = copy.deepcopy(a)
a.append(5)
a[1] = 20
a[4].append('c')
del a[0]
print(a)
print(b)
print(c)
print(d)
列表b
表示b也引用的a的地址,兩者引用的內(nèi)存地址是一樣的。因此b和a的關(guān)系是緊密相連的,一模一樣。可以通過 id(a) 和id(b)比較,兩者是一樣的。
列表c
由于c是淺拷貝的a列表,因此只copy了第一層,也就是地址層。
所以,當(dāng)a.append(5)時(shí),新增了一個(gè)內(nèi)存塊,但是c只有前5個(gè)內(nèi)存塊,因此c沒有變化。
繼續(xù)a修改了a[1],然而這個(gè)值是屬于第一層,已經(jīng)copy給了c,因此c也沒有變化。
繼續(xù)a修改了子列表,這個(gè)時(shí)候a復(fù)制給c的只是列表的地址,且a中的子列表地址和c中的子列表地址是指向同一個(gè)地方的,因此修改了a中子列表,c中的子列表也會(huì)相應(yīng)的改變。
最后刪除a[0],與修改a[1]一致,與c無關(guān)。可以用圖再說明一下。
![]()
列表d
由于d是深拷貝的a列表,因此d是將a的地址和值一并復(fù)制過來,與a沒有半點(diǎn)關(guān)系,也就是說d和a是兩個(gè)完全獨(dú)立的內(nèi)存塊,沒有任何交集。因此,后面a的任意修改都與d無關(guān),用圖表示如下。
![]()
因此,程序運(yùn)行出來后的結(jié)果就是:
a:[20,3,4,['a','b','c'],5]
b:[20,3,4,['a','b','c'],5]
c:[1,2,3,4,['a','b','c']]
d:[1,2,3,4,['a','b']]
總結(jié)
綜上,我們?cè)谑褂胏opy的時(shí)候,一定要記住:copy只是拷貝了第一層,而deepcopy才是拷貝的全部數(shù)據(jù)。
因此就不難發(fā)現(xiàn),文章背景中的代碼使用備份功能時(shí),備份列表需要使用deepcopy,而不是簡(jiǎn)單的copy。
最后:在我的V :atstudy-js,可以免費(fèi)領(lǐng)取一份10G軟件測(cè)試工程師面試寶典文檔資料。以及相對(duì)應(yīng)的視頻學(xué)習(xí)教程免費(fèi)分享!其中包括了有基礎(chǔ)知識(shí)、Linux必備、Shell、互聯(lián)網(wǎng)程序原理、Mysql數(shù)據(jù)庫、抓包工具專題、接口測(cè)試工具、測(cè)試進(jìn)階-Python編程、Web自動(dòng)化測(cè)試、APP自動(dòng)化測(cè)試、接口自動(dòng)化測(cè)試、測(cè)試高級(jí)持續(xù)集成、測(cè)試架構(gòu)開發(fā)測(cè)試框架、性能測(cè)試、安全測(cè)試等。
![]()
特別聲明:以上內(nèi)容(如有圖片或視頻亦包括在內(nèi))為自媒體平臺(tái)“網(wǎng)易號(hào)”用戶上傳并發(fā)布,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.