淺顯直白的Python深拷貝與淺拷貝區(qū)別說明
一、可變數(shù)據(jù)類型與不可變數(shù)據(jù)類型
在開始說深拷貝與淺拷貝前,我們先來弄清楚,可變對象與不可變對象
總的來說,Python數(shù)據(jù)類型可分為可變數(shù)據(jù)類型與不可變數(shù)據(jù)類型
可變數(shù)據(jù)類型:在不改變對象所指向的地址的前提下,地址中的值是可以改變的,例如列表[1, 2, 3],我們可以改為[2,3]并不需要變更它指向的地址。列表、字典、集合都是可變數(shù)據(jù)類型
不可變數(shù)據(jù)類型:在不改變對象所指向的地址的前提下,地址中的值是不可變的,所以如果修改了對象的值,就相當(dāng)于在另一個新的地址,存儲了新的值。Python中元組、字符串、數(shù)值、布爾值都是不可變數(shù)據(jù)類型。
?
二、Python深拷貝與淺拷貝的區(qū)別
在弄清楚了可變對象和不可變對象之后,我們進(jìn)入正題,看下Python的深拷貝與淺拷貝的區(qū)別
1. 淺拷貝:
僅拷貝父對象,可理解為僅拷貝對象第一層。淺拷貝之后,新舊對象本身指向的地址不同了,但子對象指向的地址仍然相同,我們可以用copy.copy()和可變數(shù)據(jù)類型的切片來進(jìn)行淺拷貝
m = [1, 0, [2, 3, 4, [5, 6]]]
n = m.copy()
p = m[:]
print(f'對象m的地址是{id(m)},對象n的地址是{id(n)},對象p的地址是{id(p)}')
print(f'm[0]的地址是{id(m[0])},n[0]的地址是{id(n[0])},p[0]的地址是{id(p[0])}')
print(f'm[2]的地址是{id(m[2])},n[2]的地址是{id(n[2])},p[2]的地址是{id(p[2])}')輸出:
對象m的地址是1322908811144,對象n的地址是1322908811080,對象p的地址是1322908763400
m[0]的地址是140727539432512,n[0]的地址是140727539432512,p[0]的地址是140727539432512
m[2]的地址是1322908811208,n[2]的地址是1322908811208,p[2]的地址是1322908811208
打印結(jié)果可以看到,淺拷貝之后,新對象n, p的地址與m不同,但n, p的子對象地址與m中子對象地址是相同的
? 此時,我們對新對象的子對象進(jìn)行修改,我們來修改一下n[0]看一下結(jié)果
n[0] =99print(f' m[0]={m[0]}\n n[0]={n[0]}\n p[0]={p[0]}')print(f' m[0]的地址:{id(m[0])}\n n[0]的地址:{id(n[0])}\n p[0]的地址:{id(p[0])}') 輸出: m[0]=1 n[0]=99 p[0]=1 m[0]的地址:140727543364672 n[0]的地址:140727543367808 p[0]的地址:140727543364672
? 可以看到,n[0]的地址和值都變了, m[0]和p[0]并沒有變,是為什么呢?
? 記得咱們最開始介紹了可變對象和不可變對象,這里的n[0]是數(shù)值,是不可變對象,所以在地址不改變的情況下,它的值是不變的;
? 而我們在給它賦值時,相當(dāng)于是把它指向了另一個地址,存儲新值,而m[0]和p[0]指向的地址并沒有變化
?
接下來咱們再來嘗試變更一下n[2]吧
print('----------------------------修改前----------------------------------\n ' ? ? ?f'm[2]的地址:{id(m[2])}\n n[2]的地址:{id(n[2])}\n p[2]的地址:{id(p[2])}') n[2][1] = 'n21'print('----------------------------修改后----------------------------------\n' ? ? ?f' m[2]={m[2]}\n n[2]={n[2]}\n p[2]={p[2]}')print(f' m[2]的地址:{id(m[2])}\n n[2]的地址:{id(n[2])}\n p[2]的地址:{id(p[2])}') 輸出:----------------------------修改前---------------------------------- m[2]的地址:2235118923976 n[2]的地址:2235118923976 p[2]的地址:2235118923976 ----------------------------修改后---------------------------------- m[2]=[2, 'n21', 4, [5, 6]] n[2]=[2, 'n21', 4, [5, 6]] p[2]=[2, 'n21', 4, [5, 6]] m[2]的地址:2235118923976 n[2]的地址:2235118923976 p[2]的地址:2235118923976
? 可以看到,變更前后n[2]、m[2]和p[2]的地址都是沒變的, 原因你已經(jīng)知道了吧,是的,因為n[2]是可變對象,是可以在地址中直接變更值的。
?
2. 深拷貝
深拷貝完全父對象與子對象??墒褂胏opy模塊的deepcopy()方法進(jìn)行深拷貝,此外使用for循環(huán)復(fù)制可迭代序列也是深拷貝
m = [1, 0, [2, 3, 4, [5, 6]]] n1 = copy.deepcopy(m)print(f'對象m的地址是{id(m)},對象n1的地址是{id(n1)}')print(f'm[0]的地址是{id(m[0])},n1[0]的地址是{id(n1[0])}')print(f'm[2]的地址是{id(m[2])},n1[2]的地址是{id(n1[2])}') 輸出: 對象m的地址是2551218893640,對象n1的地址是2551219173192 m[0]的地址是140727516494912,n1[0]的地址是140727516494912 m[2]的地址是2551218833480,n1[2]的地址是2551221308040
? ?打印結(jié)果可以看出,新舊對象本身的地址,和可變子對象地址都是不同的。
? ?這里看到m[0]和n1[0]的地址相同,但不可變對象的值變更地址就會變更,所以不會有問題。
?
三、 總結(jié)
綜上,咱們在實際應(yīng)用中,如果拷貝對象的子對象都是不可變對象,那么使用淺拷貝和深拷貝都行,
但如果待拷貝對象中有可變子對象,需要注意根據(jù)實際需求選擇使用深拷貝還是淺拷貝。