直接進入正題!
一.賦值“=”
python賦值操作的最終結果是將變量指向某個內存中的對象,只是引用。
但不同的賦值操作的中間過程是不一樣的,另一篇文章已經對賦值操作做了詳細說明:http://www.lxweimin.com/p/521bdd67790e。
總結起來就是:
1)“變量B=變量A”(變量A肯定已經指向某個對象了),對于變量之間的賦值,毫無懸念,兩個變量最終指向同一個對象。
2)“變量A=對象”(如A = 'python'),對于這種情況,如果對象在內存中不存在,那么將新建這個對象,然后將變量A指向該對象;如果對象已經存在了,那情況就稍微復雜了,部分情況是將變量指向原有的對象,部分情況會新建建一個對象(即使內存中已經有了一個值完全相同的對象),然后將變量指向這個新的對象!
二.拷貝(復制)
所謂拷貝,是完完整整地產生一個一模一樣的新對象,拷貝完成后,兩者應該是獨立的個體,不再有半點聯系。按照這個邏輯,“賦值”操作完全不屬于拷貝的范疇。
三.淺拷貝
所謂“淺拷貝”,意思是,拷貝了,但又拷貝得不徹底。淺拷貝之后,新對象確實是生成了,但在某些情況下新對象還和被拷貝對象有千絲萬縷的聯系。
淺拷貝只拷貝了最外層數據,對于子數據對象沒有拷貝,而只是一個引用(與原數據共用一個自數據對象),無論修改新數據的子對象還是元數據的子對象,另一個都會改變。
從其說明來看,對于非嵌套的數據(沒有子對象),淺拷貝實際上是完全拷貝了一個獨立的對象。
淺拷貝方法:某些對象自身的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()方法、切片操作[:]的結果與此例完全相同,不再贅述。
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()方法、切片操作[:]的結果與此例完全相同,不再贅述。
四.深拷貝
有了淺拷貝的比較,深拷貝就很簡單了,深拷貝才是真正的拷貝,無論多少嵌套,一股腦兒都拷貝一個新,是徹底的拷貝,拷貝以后與被拷貝對象不再有任何瓜葛。
深拷貝方法: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)則會被銷毀;如果只是修改一個的子對象元素,那么另一個仍然指向最原始的子對象,而不會被銷毀。
五.總結
1)賦值“=”操作,只是傳遞了一個引用,壓根不是“拷貝”的范疇;
2)淺拷貝的作用是為了節省空間,對于非嵌套數據,淺拷貝和深拷貝實際是一樣的;對于嵌套數據,淺拷貝只拷貝最外層數據,而共享子對象數據;
3)深拷貝完全拷貝真個對象,包括子對象;但并非直接生產兩個獨立的子對象,而是有一個共享子對象的中間過程(也是為了節省空間),當需要改變子對象時,才會最終執行單獨拷貝子對象的操作,而且當兩個對象都進行更改子對象操作時,兩個對象都會重新建立新的子對象,而把最初的子對象拋棄;
4)編程時,如果需要保留原數據,那么應該進行深拷貝,避免修改原數據。