哈嘍大家好,我是鵬哥。
今天想聊聊的主題是 —— 淺拷貝/深拷貝。
~~~上課鈴~~~
1
寫在前面
淺拷貝、深拷貝的知識點,在python筆試和麪試的過程中,經常被作爲考題來考察應聘者的基本功。比如問你“請簡單闡述下淺拷貝、深拷貝之間的差異”,或者給你一個變量分別進行復制、淺拷貝、深拷貝來求新值。
因此,我就簡單把自己的理解記錄下,希望對部分初學者有所幫助。
2
示例代碼
在進行示例代碼展示前,我們先理解下什麼叫 複製、淺拷貝、深拷貝。
【直接賦值】:其實就是對象的引用(別名)。
【淺拷貝(copy)】:拷貝父對象,不會拷貝對象的內部的子對象。
【深拷貝(deepcopy)】:copy 模塊的 deepcopy 方法,完全拷貝了父對象及其子對象。
好吧,反正我開始瞭解這些時對這 麼官方的介紹是看不懂的,完全不知道是 什麼意思。
Talk is cheap, Show me the code!
a = 1
# 複製
b = a
# 淺拷貝
c = copy.copy(a)
# 深拷貝
d = copy.deepcopy(a)
在進行介紹複製、淺拷貝、深拷貝前,我們還要了解下可變對象(dict/list/set)、不可變對象(int/str/float/tuple)。
【知識點1】可變對象、不可變對象
【可變對象】:當有需要改變對象內部的值的時候,這個對象的id不發生變化。
【不可變對象】:當有需要改變對象內部的值的時候,這個對象的id會發生變化。
a = 1
print(id(a), a)
a = 2
print(id(a), a)
A = [1]
print(id(A), A)
A.append(2)
print(id(A), A)
A = [1,2]
print(id(A),A)
結果:
8791349191504 1
8791349191536 2
34759304 [1]
34759304 [1, 2]
34759368 [1, 2]
從上述代碼可以看到,對不可變對象a進行值改變時,a的地址也發生了變化;但對可變對象A進行值改變時,內存地址是不變的,仍然是34759304。(這裏注意,A=[1,2]是重新賦值,所以是新地址,可以理解成是A‘,已不再是A)
【知識點2】對不可變對象進行復制、淺/深拷貝
對不可變對象,複製/淺拷貝/深拷貝都是引用原對象的內存地址。
import copy
a = 1
print(id(a), a)print(id(a), a)
# 複製
b = a
# 淺拷貝
c = copy.copy(a)
# 深拷貝
d = copy.deepcopy(a)
a = 2
print(id(a), a)
print(id(b), b)
print(id(c), c)
print(id(d), d)
結果:
8791347356496 1 -- 變化前的a
8791347356528 2 -- 變化後的a
8791347356496 1 -- 複製
8791347356496 1 -- 淺拷貝
8791347356496 1 -- 深拷貝
因此,對於不可變對象,如果原對象發生什麼變化,複製/淺拷貝/深拷貝都不會跟着變。就像對於一個又沒有上進心又窮的小夥子,無論什麼女生都不會跟着他過一生,這男的要怎麼樣就怎麼樣吧,沒人管他。
【知識點3】對可變對象進行復制、淺/深拷貝
import copy
a = [1, 2, [3, 4]]
print(id(a), a)
# 複製
b = a
# 淺拷貝
c = copy.copy(a)
# 深拷貝
d = copy.deepcopy(a)
a[1] = 5
a[2].append(6)
a.append(7)
print(id(a), a)
print(id(b), b)
print(id(c), c)
print(id(d), d)
結果:
43241288 [1, 2, [3, 4]] -- 變化前的a
43241288 [1, 5, [3, 4, 6], 7] -- 變化後的a
43241288 [1, 5, [3, 4, 6], 7] -- 複製
43311048 [1, 2, [3, 4, 6]] -- 淺拷貝
43310984 [1, 2, [3, 4]] -- 深拷貝
複製:原對象怎麼變,我跟着變。另外,這裏在可以發現對於可變對象,a變化前後的內存地址是不變的;就像老人們常說的“嫁雞隨雞,嫁狗隨狗”。
淺拷貝:原對象的外層元素地址變化,內層元素的地址不變。因爲淺拷貝是會拷貝原對象的父對象,因此只有外層元素的內存地址發生了變化,因此通過a.append(7)改變外層元素,即外層的列表時,對淺拷貝是不影響的;
那這裏你會肯定會問,爲什麼a[1]=5不變,而a[2].append(6)又發生了變化?因爲a[1]是不可變對象,a[2]是可變對象。前面已經提過了,對於不可變對象是不會跟着變的。對於可變對象的子對象變了,淺拷貝是會跟着變的。
就像上面提到的又沒有上進心又窮的小夥子,如果只是改變外觀(外層元素地址變了)或者變得有錢(窮屬性是不可變對象),是不會挽回前女友(淺拷貝)的心,只有改變了上進心(上進心屬性是可變對象),前女友纔會回心轉意而發生變化。
當然如果a[2]是通過a[2]=[3,4,6]來改變的話,淺拷貝是不會跟着變化的。原理已經在第1個知識點裏講過了,這裏不再複述。你可以理解成小夥子完全換了顆心臟,都不再是原先那個心臟了。(好吧,這個比方有點牽強。)
深拷貝:原對象的外層/內層元素地址都變化。這個就好理解了,前女友對你徹底死心,無論你怎麼變都不要你了。
綜上所述,複製相當於是無論什麼條件都願意跟着你結婚的好女孩;淺拷貝相當於有機會回到你身邊的前女友,但要看你表現;深拷貝是完全對你死心的前女友。(和面試官可別這麼說哈!)
3
補充點
如果對複製/淺拷貝/深拷貝的對象進行改變,那原對象會怎麼變呢?
import copy
a = [1, 2, [3, 4]]
print(id(a), a)
# 複製
b = a
# 淺拷貝
c = copy.copy(a)
# 深拷貝
d = copy.deepcopy(a)
b[0] = 9
print(id(a), a)
c[1] = 10
c[2].append(11)
print(id(a), a)
d[2].append(12)
print(id(a), a)
結果:
43241416 [1, 2, [3, 4]] -- 原對象a
43241416 [9, 2, [3, 4]] -- 對複製進行改變後的a
43241416 [9, 2, [3, 4, 11]] --對淺拷貝進行改變後的a
43241416 [9, 2, [3, 4, 11]] -- 對深拷貝進行改變後的a
我想大家應該能理解c[1] = 10爲什麼不改變原對象,而
c[2].append(11)
能改變原對象的原理吧 ?
4
總結
我覺得,理解複製/淺拷貝/深拷貝的關鍵還是要看對應外層元素/子層元素的地址有沒有變化。地址變了,那就完全是新的對象;如果地址不變,那地址所對應的值變化,也就跟着變化了。另外,還要看原對象是可變對象還是不可變對象。
~~~下課鈴~~~
【往期熱門文章】:
【Python成長之路】10行代碼教你免費觀看無廣告版的《慶餘年》騰訊視頻
【Python成長之路】如何用python開發自己的iphone應用程序,並添加至siri指令
【Python成長之路】從 零做網站開發 -- 基於Flask和JQuery,實現表格管理平臺
點擊下方詩句,可以留言互動喔
【關注“鵬哥賊優秀”公衆號,回覆“python學習材料”,將會有python基礎學習、機器學習、數據挖掘、高級編程教程等100G視頻資料,及100+份python相關電子書免費贈送!】
掃描二維碼
與鵬哥一起
學python吧!