詳解Python核心編程中的淺拷貝與深拷貝
一、問(wèn)題引出淺拷貝
首先看下面代碼的執(zhí)行情況:
a = [1, 2, 3]
print('a = %s' % a) # a = [1, 2, 3]
b = a
print('b = %s' % b) # b = [1, 2, 3]
a.append(4) # 對(duì)a進(jìn)行修改
print('a = %s' % a) # a = [1, 2, 3, 4]
print('b = %s' % b) # b = [1, 2, 3, 4]
b.append(5) # 對(duì)b進(jìn)行修改
print('a = %s' % a) # a = [1, 2, 3, 4, 5]
print('b = %s' % b) # b = [1, 2, 3, 4, 5]
上面的代碼比較簡(jiǎn)單,定義了一個(gè)變量a,它是一個(gè)數(shù)值[1, 2, 3]的列表,通過(guò)一個(gè)簡(jiǎn)單的賦值語(yǔ)句 b = a 定義變量b,它同樣也是數(shù)值[1, 2, 3]的列表。
問(wèn)題是:如果此時(shí)修改變量a,對(duì)b會(huì)有影響嗎?同樣如果修改變量b,對(duì)a又會(huì)有影響嗎?
從代碼運(yùn)行結(jié)果可以看出,無(wú)論是修改b還是修改a(注意這種修改的方式,是用append,直接修改原列表,而不是重新賦值),都另一方都是有影響的。
當(dāng)然這個(gè)原因其實(shí)很好理解,變量a指向的是列表[1, 2, 3]的地址值,當(dāng)用 = 進(jìn)行賦值運(yùn)算時(shí),b的值也相應(yīng)的指向的列表[1, 2, 3]的地址值。在python中,可以通過(guò)id(變量)的方法來(lái)查看地址值,我們來(lái)查看下a,b變量的地址值,看是不是相等:
# 注意,不同機(jī)器上,這個(gè)值不同,但只要a,b兩個(gè)變量的地址值是一樣的就能說(shuō)明問(wèn)題了 print(id(a)) # 4439402312 print(id(b)) # 4439402312
所以原理如下圖所示:

因此,只要是在地址值:4439402312上的列表進(jìn)行修改的話,a,b都會(huì)發(fā)生變化。(注意我這里說(shuō)的修改,是在地址值為:4439402312上的列表進(jìn)行的修改,而不說(shuō)對(duì)變量a進(jìn)行修改,因?yàn)閷?duì)變量a的修改方式有兩種,本文結(jié)尾會(huì)解釋為什么不說(shuō)對(duì)變量a進(jìn)行修改) 。所以我們便引出了以下概念:
對(duì)于這種是將引用進(jìn)行拷貝賦值給另一個(gè)變量的方式(即拷貝的是地址值),我們稱(chēng)之為淺拷貝。
二、如何進(jìn)行深拷貝
python中實(shí)現(xiàn)深拷貝的方式很簡(jiǎn)單,只需要引入copy模塊,調(diào)用里面的deepcopy()的方法即可,示例代碼如下:
import copy
a = [1, 2, 3]
b = copy.deepcopy(a)
print('a = %s' % a) # a = [1, 2, 3]
print('b = %s' % b) # b = [1, 2, 3]
b.append(4)
print('a = %s' % a) # a = [1, 2, 3]
print('b = %s' % b) # b = [1, 2, 3, 4]
從代碼執(zhí)行情況來(lái)看,我們已經(jīng)實(shí)現(xiàn)了深拷貝。這時(shí)我們?cè)賮?lái)看下兩個(gè)變量的地址值:
print(id(a)) # 4321416008 print(id(b)) # 4321416200
果然就不一樣了。我們?cè)偻ㄟ^(guò)一個(gè)圖來(lái)看下深拷貝的原理:

三、copy模塊方法簡(jiǎn)介
從深拷貝的實(shí)現(xiàn)過(guò)程,我們知道copy模塊,也使用了里面的deepcopy()方法。下面我們來(lái)介紹下copy模塊中的copy()與deepcopy()方法。
首先介紹我們已經(jīng)使用過(guò)的deepcopy()方法,官方文檔介紹如下:

簡(jiǎn)單解釋下文檔中對(duì)這個(gè)方法的說(shuō)明:
1. 返回值是對(duì)這個(gè)對(duì)象的深拷貝
2. 如果拷貝發(fā)生錯(cuò)誤,會(huì)報(bào)copy.err異常
3. 存在兩個(gè)問(wèn)題,第一是如果出遞歸對(duì)象,會(huì)遞歸的進(jìn)行拷貝,第二正因?yàn)闀?huì)遞歸拷貝,會(huì)導(dǎo)致出現(xiàn)拷貝過(guò)多的情況
4. 關(guān)于兩種拷貝方式的區(qū)別都是相對(duì)是引用對(duì)象
前兩點(diǎn)很好理解,針對(duì)第三點(diǎn),我們用代碼進(jìn)行解釋?zhuān)?/p>
import copy
a = [1, 2, 3]
b = [3, 4, 5]
c = [a, b] # 列表嵌套
d = copy.deepcopy(c)
print('c = %s' % c) # c = [[1, 2, 3], [3, 4, 5]]
print('d = %s' % d) # d = [[1, 2, 3], [3, 4, 5]]
c.append(4)
print('c = %s' % c) # c = [[1, 2, 3], [3, 4, 5], 4]
print('d = %s' % d) # d = [[1, 2, 3], [3, 4, 5]]
c[0].append(4) # 相當(dāng)于a.append(4)
print('c = %s' % c) # c = [[1, 2, 3, 4], [3, 4, 5], 4]
print('d = %s' % d) # d = [[1, 2, 3], [3, 4, 5]]
# a.append(4)
# print('c = %s' % c) # a = [1, 2, 3]
# print('d = %s' % d) # b = [1, 2, 3]
print(id(c)) # 4314188040
print(id(d)) # 4314187976
print(id(c[0])) # 4314186568
print(id(d[0])) # 4314187912
print(id(a)) # 4314186568
print(id(b)) # 4314186760
根據(jù)代碼,我們可以看到,當(dāng)有嵌套對(duì)象,也就是文檔中提到的遞歸對(duì)象,從結(jié)果我們可以看到,嵌套對(duì)象會(huì)進(jìn)行遞歸的深拷貝。即如果c里有一個(gè)a,那么不僅c會(huì)深拷貝,a同樣也會(huì)被深拷貝。原理如下圖所求:

接下來(lái)我們?cè)賮?lái)看copy()方法:
官方文檔解釋的很簡(jiǎn)單,它返回的就是對(duì)象的淺拷貝。但其實(shí)它會(huì)對(duì)最外層進(jìn)行深拷貝,而如果有多層,第二層以后進(jìn)行的就是淺拷貝了。代碼示例如下:
import copy
a = [1, 2, 3]
b = [3, 4, 5]
c = [a, b] # 列表嵌套
d = copy.copy(c)
print('c = %s' % c) # c = [[1, 2, 3], [3, 4, 5]]
print('d = %s' % d) # d = [[1, 2, 3], [3, 4, 5]]
c.append(4)
print('c = %s' % c) # c = [[1, 2, 3], [3, 4, 5], 4]
print('d = %s' % d) # d = [[1, 2, 3], [3, 4, 5]] 沒(méi)有發(fā)生變化,說(shuō)明外層是深拷貝
c[0].append(4) # 相當(dāng)于a.append(4)
print('c = %s' % c) # c = [[1, 2, 3, 4], [3, 4, 5], 4]
print('d = %s' % d) # d = [[1, 2, 3, 4], [3, 4, 5]] 發(fā)生了變化,說(shuō)明內(nèi)層是淺拷貝
# a.append(4)
# print('c = %s' % c) # c = [[1, 2, 3, 4], [3, 4, 5], 4]
# print('d = %s' % d) # d = [[1, 2, 3, 4], [3, 4, 5]] 發(fā)生了變化,說(shuō)明內(nèi)層是淺拷貝
print(id(c)) # 4322576648
print(id(d)) # 4322576584 d和c地址不同,進(jìn)一步說(shuō)明外層是深拷貝
print(id(c[0])) # 4322575176
print(id(d[0])) # 4322575176 c[0]和d[0]地址相同,進(jìn)一步說(shuō)明內(nèi)層是淺拷貝
print(id(a)) # 4322575176
print(id(b)) # 4322575368
【注意】對(duì)于copy()方法,有特殊情況,比如元組類(lèi)型,代碼示例如下:
import copy a = [1, 2, 3] b = [3, 4, 5] c = (a, b) # 列表改成元組 d = copy.copy(c) print(id(c)) # 4303015752 print(id(d)) # 4303015752 d和c地址相同 print(id(c[0])) # 4322575176 print(id(d[0])) # 4322575176 c[0]和d[0]地址相同,進(jìn)一步說(shuō)明內(nèi)層是淺拷貝
可以看到,這里哪怕是最外層,也是淺拷貝。
這里因?yàn)閏opy方法內(nèi)部有判斷,如果最外層的拷貝類(lèi)型是不可變類(lèi)型,則進(jìn)行淺拷貝,反之則進(jìn)行深拷貝。
至此,我們應(yīng)該對(duì)淺拷貝的概念進(jìn)行進(jìn)一步加深理解:
如果對(duì)象中的所有元素,有一個(gè)是引用拷貝,則定義為是淺拷貝。(該定義不是官方定義,只是個(gè)人理解)
四、關(guān)于“修改”的一點(diǎn)說(shuō)明
前面提到了修改變量,我認(rèn)為修改是有兩種方式,第一種在原對(duì)象上進(jìn)行修改,第二種就是重新賦值。看如下代碼:
import copy a = [1, 2, 3] b = a a = [3, 4, 5] print(a) # [3, 4, 5] print(b) # [1, 2, 3]
同樣是淺拷貝,但是發(fā)現(xiàn)修改a之后,b沒(méi)有發(fā)生變化。
在修改的時(shí)候,我們很容易想當(dāng)然的通過(guò)重新賦值的方式來(lái)修改,但其實(shí)這種修改方式是有問(wèn)題的。當(dāng)給a再次賦值的時(shí)候,其實(shí)是將a重新指向了另外一塊地址區(qū)域,而原來(lái)的[1, 2, 3]那塊地址區(qū)域是沒(méi)有發(fā)生任何變化的,所以對(duì)于b來(lái)說(shuō),它指向的東西并沒(méi)有改變。
這也解釋了之前文檔中關(guān)于deepcopy方法的一個(gè)說(shuō)明,為什么只對(duì)引用對(duì)象有用,因?yàn)楹?jiǎn)單類(lèi)型修改的方式就是重新賦值。簡(jiǎn)單理解就是你沒(méi)辦法通過(guò)簡(jiǎn)單類(lèi)型的變量直接通過(guò).來(lái)調(diào)用自身的方法,都只能重新賦值來(lái)改變,那么都會(huì)指向新的地址。

以上就是本片文章關(guān)于Python核心編程中的淺拷貝與深拷貝的全部?jī)?nèi)容,大家可以把學(xué)習(xí)的心得寫(xiě)在下面的留言區(qū),感謝你對(duì)腳本之家的支持。
- Python 拷貝對(duì)象(深拷貝deepcopy與淺拷貝copy)
- Python對(duì)象的深拷貝和淺拷貝詳解
- Python中的賦值、淺拷貝、深拷貝介紹
- 對(duì)Python中列表和數(shù)組的賦值,淺拷貝和深拷貝的實(shí)例講解
- 深入理解python中的淺拷貝和深拷貝
- 淺談Python淺拷貝、深拷貝及引用機(jī)制
- Python中的復(fù)制操作及copy模塊中的淺拷貝與深拷貝方法
- Python中字典的淺拷貝與深拷貝用法實(shí)例分析
- 深入淺析Python中l(wèi)ist的復(fù)制及深拷貝與淺拷貝
- Python中淺拷貝copy與深拷貝deepcopy的簡(jiǎn)單理解
- Python深拷貝與淺拷貝用法實(shí)例分析
相關(guān)文章
Python-numpy實(shí)現(xiàn)灰度圖像的分塊和合并方式
今天小編就為大家分享一篇Python-numpy實(shí)現(xiàn)灰度圖像的分塊和合并方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-01-01
Python進(jìn)階之遞歸函數(shù)的用法及其示例
本篇文章主要介紹了Python進(jìn)階之遞歸函數(shù)的用法及其示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-01-01
Python 檢查數(shù)組元素是否存在類(lèi)似PHP isset()方法
isset方法來(lái)檢查數(shù)組元素是否存在,在Python中無(wú)對(duì)應(yīng)函數(shù),在Python中一般可以通過(guò)異常來(lái)處理數(shù)組元素不存在的情況,而無(wú)須事先檢查2014-10-10
python 禁止函數(shù)修改列表的實(shí)現(xiàn)方法
下面小編就為大家?guī)?lái)一篇python 禁止函數(shù)修改列表的實(shí)現(xiàn)方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-08-08
Python3爬蟲(chóng)發(fā)送請(qǐng)求的知識(shí)點(diǎn)實(shí)例
在本篇文章里小編給大家分享的是一篇關(guān)于Python3爬蟲(chóng)發(fā)送請(qǐng)求的知識(shí)點(diǎn)實(shí)例,需要的朋友們可以學(xué)習(xí)下。2020-07-07
Python數(shù)據(jù)結(jié)構(gòu)與算法之算法分析詳解
算法分析的主要目標(biāo)是從運(yùn)行時(shí)間和內(nèi)存空間消耗等方面比較算法。本文將為大家詳細(xì)介紹Python數(shù)據(jù)結(jié)構(gòu)與算法中的算法分析,需要的可以參考一下2021-12-12
Python進(jìn)行數(shù)據(jù)科學(xué)工作的簡(jiǎn)單入門(mén)教程
這篇文章主要介紹了Python進(jìn)行數(shù)據(jù)科學(xué)工作的簡(jiǎn)單入門(mén)教程,主要針對(duì)Python發(fā)行版Anaconda進(jìn)行說(shuō)明,需要的朋友可以參考下2015-04-04
python文字轉(zhuǎn)語(yǔ)音實(shí)現(xiàn)過(guò)程解析
這篇文章主要介紹了python文字轉(zhuǎn)語(yǔ)音實(shí)現(xiàn)過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11
openCV入門(mén)學(xué)習(xí)基礎(chǔ)教程第二篇
人臉識(shí)別,物體檢測(cè),OpenCV是基石,下面這篇文章主要給大家介紹了關(guān)于openCV入門(mén)學(xué)習(xí)基礎(chǔ)教程的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-11-11

