Python中的賦值、淺拷貝、深拷貝

1.jpg

直接進入正題!

一.賦值“=”

python賦值操作的最終結果是將變量指向某個內存中的對象,只是引用。
但不同的賦值操作的中間過程是不一樣的,另一篇文章已經對賦值操作做了詳細說明:http://www.lxweimin.com/p/521bdd67790e
總結起來就是:
1)“變量B=變量A”(變量A肯定已經指向某個對象了),對于變量之間的賦值,毫無懸念,兩個變量最終指向同一個對象。
2)“變量A=對象”(如A = 'python'),對于這種情況,如果對象在內存中不存在,那么將新建這個對象,然后將變量A指向該對象;如果對象已經存在了,那情況就稍微復雜了,部分情況是將變量指向原有的對象,部分情況會新建建一個對象(即使內存中已經有了一個值完全相同的對象),然后將變量指向這個新的對象!

賦值過程.jpg

二.拷貝(復制)

所謂拷貝,是完完整整地產生一個一模一樣的新對象,拷貝完成后,兩者應該是獨立的個體,不再有半點聯系。按照這個邏輯,“賦值”操作完全不屬于拷貝的范疇

三.淺拷貝

所謂“淺拷貝”,意思是,拷貝了,但又拷貝得不徹底。淺拷貝之后,新對象確實是生成了,但在某些情況下新對象還和被拷貝對象有千絲萬縷的聯系。

淺拷貝只拷貝了最外層數據,對于子數據對象沒有拷貝,而只是一個引用(與原數據共用一個自數據對象),無論修改新數據的子對象還是元數據的子對象,另一個都會改變。

從其說明來看,對于非嵌套的數據(沒有子對象),淺拷貝實際上是完全拷貝了一個獨立的對象。

淺拷貝方法:某些對象自身的copy()方法、copy模塊中的copy()方法、切片操作[:]。

實例:

1)淺拷貝“非嵌套數據對象”
>>>a = [1,2,3]
>>>b = a.copy() #淺拷貝a給b
>>>print(id(a))
>>>print(id(b))
41921864
41943176

a[0] = 'A' #修改a的元素
print('a={}'.format(a))
print('b={}'.format(b))
a=['A', 2, 3]
b=[1, 2, 3]

上例中[1,2,3]是一個非嵌套的列表數據,b = a.copy()是將a淺拷貝一份然后指向b,從id可以看出,b指向的是一個與a相互獨立的對象。后面修改了a指向的對象元素后,b指向的對象沒有變化,進一步說明了兩者的獨立性。copy模塊中的copy()方法、切片操作[:]的結果與此例完全相同,不再贅述。


淺拷貝非嵌套數據.jpg
2)淺拷貝“嵌套數據對象”
>>>a = [1,2,['A','B']]
>>>b = a.copy()
>>>print(id(a))
>>>print(id(b))
36044936
36047688

>>>print(id(a[2][0]))
>>>print(id(b[2][0]))
34525456
34525456

>>>a[0] = 'P' #修改a的外圈元素
>>>a[2][0] = 'Q' #修改a的子對象元素
print('a={}'.format(a))
print('b={}'.format(b))
a=['P', 2, ['Q', 'B']]
b=[1, 2, ['Q', 'B']]

此例中[1,2,['A','B']]是個嵌套列表,b = a.copy()只會拷貝最外層數據給新的對象,然后指向b,而子對象['A','B']則不會拷貝,a和b共同指向該子對象。id(a)≠id(b),說明了a.copy()確實只拷貝了外層對象,而id(a[2][0])=id(b[2][0])說明子對象是共用的,沒重新拷貝。后面a[0] = 'P'和a[2][0] = 'Q'分別修改了列表的外圈元素和子對象元素,從打印結果可以看出b的外圈元素沒變,而內圈元素變了,進一步說明了外圈是獨立對象而子對象是共享的。
copy模塊中的copy()方法、切片操作[:]的結果與此例完全相同,不再贅述。


淺拷貝嵌套數據.jpg

四.深拷貝

有了淺拷貝的比較,深拷貝就很簡單了,深拷貝才是真正的拷貝,無論多少嵌套,一股腦兒都拷貝一個新,是徹底的拷貝,拷貝以后與被拷貝對象不再有任何瓜葛。
深拷貝方法:copy中的deepcopy()方法

實例:

1)深拷貝“非嵌套數據對象”

這種情況與前面的“淺拷貝“非嵌套數據對象””一模一樣,不再贅述。

2)深拷貝“嵌套數據對象”
import copy

>>>a = [1,2,['A','B']]
>>>b = copy.deepcopy(a) #深拷貝
>>>print(id(a))
>>>print(id(b))
42275400
42276680
>>>print(id(a[2][0]))
>>>print(id(b[2][0]))
34328848
34328848

上例中我們驚奇地發現id(a[2][0])=id(b[2][0]),這不是和淺拷貝一模一樣嗎?沒有我們所謂的獨立拷貝一個啊?這是怎么回事?一開始筆者也非常納悶。研究發現實際上這是Python節省內存的方式,雖然是深拷貝,但我先不急著拷貝子對象,先共用,如果后面程序需要單獨用到這些數據時,再執行拷貝子對象這個動作。接著上面的代碼:

>>>a[2][0] = 'Q' #修改a的子對象元素
>>>b[2][1] = 'QQ' #修該b的子對象元素

>>>print('a={}'.format(a))
>>>print('b={}'.format(b))
a=['1', 2, ['Q', 'B']]
b=['1', 2, ['A', 'QQ']]

>>>print(id(a[2][0]))
>>>print(id(b[2][0]))
39764128
39764408

我們通過a[2][0] = 'Q'修改a的子對象的第一個元素,通過b[2][1] = 'QQ'修改b的子對象的第二個元素,根據輸出結果a=['1', 2, ['Q', 'B']]和b=['1', 2, ['A', 'QQ']]兩者確實相互不影響,說明是獨立的。此時我們再輸出第一個子對象的地址,發現id(a[2][0])≠id(b[2][0])了,說明此時子對象已經是兩個獨立的對象了,而且兩者都不等于原來子對象的內存地址34328848。這個結果說明了兩個問題:1)深度拷貝后,需要對子對象操作時,才會真正執行拷貝一個子對象的動作;2)深拷貝后,如果無需要對兩個對象的子對象元素進行修改,那么兩者都會先重新生成一個子對象(也就是這里的39764128和39764408),然后再修改,至于最開始的那個子對象(上面的34328848)則會被銷毀;如果只是修改一個的子對象元素,那么另一個仍然指向最原始的子對象,而不會被銷毀。

深拷貝嵌套數據.jpg

五.總結

1)賦值“=”操作,只是傳遞了一個引用,壓根不是“拷貝”的范疇;
2)淺拷貝的作用是為了節省空間,對于非嵌套數據,淺拷貝和深拷貝實際是一樣的;對于嵌套數據,淺拷貝只拷貝最外層數據,而共享子對象數據;
3)深拷貝完全拷貝真個對象,包括子對象;但并非直接生產兩個獨立的子對象,而是有一個共享子對象的中間過程(也是為了節省空間),當需要改變子對象時,才會最終執行單獨拷貝子對象的操作,而且當兩個對象都進行更改子對象操作時,兩個對象都會重新建立新的子對象,而把最初的子對象拋棄;
4)編程時,如果需要保留原數據,那么應該進行深拷貝,避免修改原數據。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Swift1> Swift和OC的區別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,136評論 1 32
  • 1.ios高性能編程 (1).內層 最小的內層平均值和峰值(2).耗電量 高效的算法和數據結構(3).初始化時...
    歐辰_OSR閱讀 29,566評論 8 265
  • __block和__weak修飾符的區別其實是挺明顯的:1.__block不管是ARC還是MRC模式下都可以使用,...
    LZM輪回閱讀 3,363評論 0 6
  • 轉眼2017年下半年都已經過去一個月了,上半年平平淡淡,想學手繪就零零散散的畫了幾個月也沒有多大進步,想學的東西太...
    瘋狂的小南瓜閱讀 357評論 0 0
  • 《冰激凌快化了》 冰激凌快化了 趕緊吃 快快快 別催了 看,手上,嘴邊,胸前全是 慢慢吃 吃到凌晨 吃到一只貓出來...
    鋤風少年閱讀 212評論 0 2