Python中深淺拷貝的區(qū)別詳細(xì)分析

簡(jiǎn)而言之:
深淺拷貝的區(qū)別關(guān)鍵在于拷貝的對(duì)象類型是否可變。
我們可以總結(jié)出以下三條規(guī)則:
- 對(duì)于可變對(duì)象來說,深拷貝和淺拷貝都會(huì)開辟新地址,完成對(duì)象的拷貝
- 而對(duì)于不可變對(duì)象來說,深淺拷貝都不會(huì)開辟新地址,只是建立引用關(guān)聯(lián)
,等價(jià)于賦值- 對(duì)于復(fù)合對(duì)象來說,淺拷貝只考慮最外層的類型,復(fù)合類型數(shù)據(jù)中的元
素仍為引用關(guān)系;而深拷貝對(duì)復(fù)合對(duì)象會(huì)遞歸應(yīng)用前兩條規(guī)則
背后的邏輯也很容易理解,我們可以在 Python 的官方文檔里找到如下解釋:
Python 的賦值語(yǔ)句不復(fù)制對(duì)象,而是創(chuàng)建目標(biāo)和對(duì)象的綁定關(guān)系。對(duì)于自身可變,或包含可變項(xiàng)的集合,有時(shí)要生成副本用于改變操作,而不必改變?cè)紝?duì)象。
- 不可變數(shù)據(jù)(3 個(gè)):Number(數(shù)字)、String(字符串)、Tuple(元組);
- 可變數(shù)據(jù)(3 個(gè)):List(列表)、Dictionary(字典)、Set(集合)。
下面我們通過對(duì)不同類型的對(duì)象進(jìn)行深淺拷貝來逐條說明上述規(guī)則:
不可變對(duì)象
以元組(tuple)為例:
import copy tup1 = (991, "abc") tup2 = copy.copy(tup1) # 淺拷貝 # tup2 = tup1 # 在這個(gè)例子里淺拷貝等價(jià)于賦值 print(id(tup1)) print(id(tup2)) print(tup1 == tup2) print(tup1 is tup2) # 2457279675264 # 2457279675264 # True # True tup2 = copy.deepcopy(tup1) # 深拷貝 print(id(tup1)) print(id(tup2)) print(tup1 == tup2) print(tup1 is tup2) # 1291830377344 # 1291830377344 # True # True
我們可以看到,對(duì)于不可變對(duì)象,深拷貝還是淺拷貝都不會(huì)為我們對(duì)象建立真正的副本,tup2 和 tup1的地址完全相同,實(shí)際上引用的是同一個(gè)對(duì)象。
可變對(duì)象
以列表(list)為例:
import copy
lis1 = [991, "abc", (9, 993), [994, 995], [888,887], {"name": "Tom"}, (996, [997, 998]), (888,(886, 886))]
lis2 = copy.copy(lis1) # 淺拷貝
print(id(lis1))
print(id(lis2))
print(lis1 == lis2)
print(lis1 is lis2)
# 2491304912896
# 2491304912960
# True
# False
lis2 = copy.deepcopy(lis1) # 深拷貝
print(id(lis1))
print(id(lis2))
print(lis1 == lis2)
print(lis1 is lis2)
# 2841088174144
# 2841088174208
# True
# False可以看到,對(duì)于可變對(duì)象來說,深拷貝和淺拷貝都會(huì)開辟新地址,完成對(duì)象的拷貝。
復(fù)合對(duì)象
其實(shí)上面例子中的列表同時(shí)還是一個(gè)復(fù)合對(duì)象(即包含其他對(duì)象的對(duì)象)。
對(duì)于復(fù)合對(duì)象來說,淺拷貝只考慮最外層的類型,復(fù)合類型數(shù)據(jù)中的元素仍為引用關(guān)系。深拷貝對(duì)復(fù)合對(duì)象會(huì)遞歸應(yīng)用前兩條規(guī)則。
import copy
tup3 = (991, "abc", [])
tup4 = copy.copy(tup3) # 淺拷貝
print(tup3 is tup4) # True
print(tup3[-1] is tup4[-1]) # True
lis1 = [991, "abc", (9, 993), [994, 995], [888,887], {"name": "Tom"}, (996, [997, 998]), (888,(886, 886))]
lis2 = copy.copy(lis1) # 淺拷貝
print(lis1 is lis2) # False
# 雖然 lis1 和 lis2 的地址不同,但其中的每個(gè)元素都各自指向同一個(gè)對(duì)象
print(lis1[0] is lis2[0]) # True
print(lis1[1] is lis2[1]) # True
print(lis1[2] is lis2[2]) # True
print(lis1[3] is lis2[3]) # True
print(lis1[4] is lis2[4]) # True
print(lis1[5] is lis2[5]) # True
print(lis1[6] is lis2[6]) # True
print(lis1[7] is lis2[7]) # True可以看到對(duì)于復(fù)合對(duì)象,其最外層的邏輯和前文提到的相同,即可變對(duì)象開辟新地址,不可變對(duì)象不開辟新地址。但復(fù)合對(duì)象內(nèi)的元素全部只是建立引用關(guān)聯(lián),地址相同。
而深拷貝還需確認(rèn)復(fù)合對(duì)象中的所有元素是否都不可變?nèi)缓笤趯?duì)元素遞歸應(yīng)用前兩條規(guī)則。只要復(fù)合對(duì)象本身是可變的或者其中存在可變對(duì)象,則都會(huì)完成拷貝。
import copy
lis1 = [991, "abc", (9, 993), [994, 995], [888,887], {"name": "Tom"}, (996, [997, 998]), (888,(886, 886))]
lis2 = copy.deepcopy(lis1) # 深拷貝
print(lis1 is lis2) # False,列表是可變對(duì)象,深復(fù)制后地址改變
print(lis1[0] is lis2[0]) # True,索引0是整數(shù),不可變,地址不變
print(lis1[3] is lis2[3]) # False, 索引3是列表,可變,地址改變
tup3 = (991, "abc", [])
tup4 = copy.deepcopy(tup3) # 深拷貝
print(tup3 is tup4) # False,雖然 tup3 是不可變對(duì)象,但其內(nèi)部存在可變對(duì)象,所以深復(fù)制后地址仍然改變
print(tup3[0] is tup4[0]) # True,索引 0 是整數(shù),不可變,地址不變
print(tup3[1] is tup4[1]) # True,索引 3 是字符串,不可變,地址不變
print(tup3[2] is tup4[2]) # False,索引 3 是列表,可變,地址改變參考:
copy — Shallow and deep copy operations — Python 3.10.7 documentation(Python3 文檔)
補(bǔ)充:下面解釋可變類型和不可變類型的嵌套使用
(1)可變類型:
淺拷貝和深拷貝只要最外層是可變類型都會(huì)生成新的對(duì)象
- [] 或者{}, 淺拷貝和深拷貝都會(huì)生成新的對(duì)象
- [[],[]]列表的嵌套,可變類型嵌套了可變類型,淺拷貝:只拷貝最外層,會(huì)生成新的對(duì)象,內(nèi)層是引用。深拷貝:外層和內(nèi)層都會(huì)進(jìn)行拷貝,都是全新的對(duì)象,都有獨(dú)立的存儲(chǔ)空間
- [(),()] 外層可變,內(nèi)層不可變,淺拷貝:只拷貝最外層,會(huì)生成新的對(duì)象,內(nèi)層是引用。深拷貝:外層和內(nèi)層都會(huì)進(jìn)行拷貝,外層會(huì)生成新對(duì)象,但是由于內(nèi)層是不可變類型,所以內(nèi)層依然是引用
- [(),[]] 外層可變,內(nèi)層有一個(gè)是可變,淺拷貝:只拷貝最外層,會(huì)生成新的對(duì)象,內(nèi)層是引用。深拷貝:外層和內(nèi)層都會(huì)進(jìn)行拷貝,外層會(huì)生成新對(duì)象,內(nèi)層可變對(duì)象會(huì)生成新對(duì)象,內(nèi)層不可變對(duì)象是引用
(2)不可變類型:
最外層是不可變類型,淺拷貝就一定是引用
- Number、字符串或者(), 淺拷貝和深拷貝都是引用
- ([],[]), copy淺拷貝:只會(huì)拷貝最外層,內(nèi)層只是引用,但是最外層是不可變,拷貝之后毫無意義,僅僅是引用關(guān)系。deepcopy:從外層到內(nèi)層都會(huì)拷貝,內(nèi)層是可變,為了達(dá)到和原來的數(shù)據(jù)完全隔離,會(huì)生成全新的對(duì)象
- ((),()) 完全不可變,拷貝了之后如果生成新的數(shù)據(jù)也無法修改,所以不管深拷貝還是淺拷貝都是引用
- ((),[]) 外層不可變,但是內(nèi)層有一個(gè)是可變,copy依然是引用,deepcopy,會(huì)生成新的對(duì)象,內(nèi)層的不可變類型是引用,可變類型會(huì)生成新的對(duì)象。
總結(jié)
到此這篇關(guān)于Python中深淺拷貝的區(qū)別的文章就介紹到這了,更多相關(guān)Python深淺拷貝的區(qū)別內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
flask/django 動(dòng)態(tài)查詢表結(jié)構(gòu)相同表名不同數(shù)據(jù)的Model實(shí)現(xiàn)方法
今天小編就為大家分享一篇flask/django 動(dòng)態(tài)查詢表結(jié)構(gòu)相同表名不同數(shù)據(jù)的Model實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-08-08
Python3 翻轉(zhuǎn)二叉樹的實(shí)現(xiàn)
這篇文章主要介紹了Python3 翻轉(zhuǎn)二叉樹的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09
python pandas多數(shù)據(jù)操作的完整指南
在數(shù)據(jù)分析工作中,我們經(jīng)常需要處理多個(gè)數(shù)據(jù)集并將它們以各種方式組合起來,Pandas 提供了多種強(qiáng)大的多數(shù)據(jù)操作方法,下面就跟隨小編一起了解一下吧2025-04-04
Python3實(shí)現(xiàn)的簡(jiǎn)單驗(yàn)證碼識(shí)別功能示例
這篇文章主要介紹了Python3實(shí)現(xiàn)的簡(jiǎn)單驗(yàn)證碼識(shí)別功能,涉及Python針對(duì)驗(yàn)證碼圖片識(shí)別處理相關(guān)操作技巧,需要的朋友可以參考下2018-05-05
Django框架配置mysql數(shù)據(jù)庫(kù)實(shí)現(xiàn)過程
這篇文章主要介紹了Django框架配置mysql數(shù)據(jù)庫(kù)實(shí)現(xiàn)過程,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04
python 使用三引號(hào)時(shí)容易犯的小錯(cuò)誤
這篇文章主要介紹了python 使用三引號(hào)時(shí)容易犯的小錯(cuò)誤,幫助新手學(xué)習(xí),避免入坑,感興趣的朋友可以了解下2020-10-10
python實(shí)現(xiàn)從ftp服務(wù)器下載文件的方法
這篇文章主要介紹了python實(shí)現(xiàn)從ftp服務(wù)器下載文件的方法,涉及Python操作FTP的相關(guān)技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-04-04
pycharm訪問mysql數(shù)據(jù)庫(kù)的方法步驟
這篇文章主要介紹了pycharm訪問mysql數(shù)據(jù)庫(kù)的方法步驟。文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06

