和很多語言一樣,Python中也分為簡單賦值、淺拷貝、深拷貝這幾種“拷貝”方式。
在學習過程中,一開始對淺拷貝理解很模糊。不過經過一系列的實驗后,我發現對這三者的概念有了進一步的了解。
一、賦值
賦值算是這三種操作中最常見的了,我們通過一些例子來分析下賦值操作:
str例
代碼如下:>>>a='hello'>>>b='hello'>>>c=a>>>[id(x)forxina,b,c][4404120000,4404120000,4404120000]由以上指令中,我們可以發現a,b,c三者的地址是一樣的。所以以上賦值的操作就相當于c=a=b='hello'。
賦值是系統先給一個變量或者對象(這里是'hello')分配了內存,然后再將地址賦給a,b,c。所以它們的地址是相同的。
list例
代碼如下:>>>a=['hello']>>>b=['hello']>>>c=a>>>[id(x)forxina,b,c][4403975952,4404095096,4403975952]但是這種情況卻不一樣了,a和b的地址不同。為何?
因為str是不可變的,所以同樣是'hello'只有一個地址,但是list是可變的,所以必須分配兩個地址。
這時,我們希望探究以上兩種情況如果修改值會如何?
str例
代碼如下:>>>a='world'>>>[id(x)forxina,b,c][4404120432,4404120000,4404120000]>>>printa,b,cworldhellohello這時a的地址和值變了,但是b,c地址和值都未變。因為str的不可變性,a要重新賦值則需重新開辟內存空間,所以a的值改變,a指向的地址改變。b,c由于'hello'的不變性,不會發生改變。
list例
代碼如下:>>>a[0]='world'>>>[id(x)forxina,b,c][4403975952,4404095096,4403975952]>>>printa,b,c['world']['hello']['world']這時a,c的值和地址均改變,但二者仍相同,b不改變。由于list的可變性,所以修改list的值不需要另外開辟空間,只需修改原地址的值。所以a,c均改變。
在了解了以上的不同點之后,我們就能很好地分析淺拷貝和深拷貝了。
我們均用list作為例子。
二、淺拷貝
代碼如下:>>>a=['hello',[123,234]]>>>b=a[:]>>>[id(x)forxina,b][4496003656,4496066752]>>>[id(x)forxina][4496091584,4495947536]>>>[id(x)forxinb][4496091584,4495947536]Line3,4可以看出a,b地址不同,這符合list是可變的,應開辟不同空間。那淺拷貝就是拷貝了一個副本嗎?再看Line5-8,我們發現a,b中元素的地址是相同的。如果說字符串'hello'地址一致還能理解,但是第二個元素是list地址仍一致。這就說明了淺拷貝的特點,只是將容器內的元素的地址復制了一份。
接著我們嘗試修改a,b中的值:
代碼如下:>>>a[0]='world'>>>a[1].append(345)>>>print'a=',a,'\n\r','b=',ba=['world',[123,234,345]]b=['hello',[123,234,345]]a中第一個元素str改變,但是b中未改變;a中第二個元素改變,b中也改變。這就符合不可變的對象修改會開辟新的空間,可變的對象修改不會開辟新空間。也進一步證明了淺拷貝僅僅是復制了容器中元素的地址。
三、深拷貝
代碼如下:>>>fromcopyimportdeepcopy>>>a=['hello',[123,234]]>>>b=deepcopy(a)>>>[id(x)forxina,b][4496066824,4496066680]>>>[id(x)forxina][4496091584,4496067040]>>>[id(x)forxinb][4496091584,4496371792]深拷貝后,可以發現a,b地址以及a,b中元素地址均不同。這才是完全拷貝了一個副本。
修改a的值后:
從Line4,5中可以發現僅僅a修改了,b沒有任何修改。因為b是一個完全的副本,元素地址均與a不同,a修改,b不受影響。
總結:
1.賦值是將一個對象的地址賦值給一個變量,讓變量指向該地址(舊瓶裝舊酒)。
2.淺拷貝是在另一塊地址中創建一個新的變量或容器,但是容器內的元素的地址均是源對象的元素的地址的拷貝。也就是說新的容器中指向了舊的元素(新瓶裝舊酒)。
3.深拷貝是在另一塊地址中創建一個新的變量或容器,同時容器內的元素的地址也是新開辟的,僅僅是值相同而已,是完全的副本。也就是說(新瓶裝新酒)。