一、可變資料型別與不可變資料型別
在開始說深拷貝與淺拷貝前,我們先來弄清楚,可變物件與不可變物件
總的來說,Python資料型別可分為可變資料型別與不可變資料型別
可變資料型別:在不改變物件所指向的地址的前提下,地址中的值是可以改變的,例如列表[1, 2, 3],我們可以改為[2,3]並不需要變更它指向的地址。列表、字典、集合都是可變資料型別
不可變資料型別:在不改變物件所指向的地址的前提下,地址中的值是不可變的,所以如果修改了物件的值,就相當於在另一個新的地址,儲存了新的值。Python中元組、字串、數值、布林值都是不可變資料型別。
二、Python深拷貝與淺拷貝的區別
在弄清楚了可變物件和不可變物件之後,我們進入正題,看下Python的深拷貝與淺拷貝的區別
1. 淺拷貝:
僅拷貝父物件,可理解為僅拷貝物件第一層。淺拷貝之後,新舊物件本身指向的地址不同了,但子物件指向的地址仍然相同,我們可以用copy.copy()和可變資料型別的切片來進行淺拷貝
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
列印結果可以看到,淺拷貝之後,新物件n, p的地址與m不同,但n, p的子物件地址與m中子物件地址是相同的
此時,我們對新物件的子物件進行修改,我們來修改一下n[0]看一下結果
n[0] =99 print(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]是數值,是不可變物件,所以在地址不改變的情況下,它的值是不變的;
而我們在給它賦值時,相當於是把它指向了另一個地址,儲存新值,而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]的地址都是沒變的, 原因你已經知道了吧,是的,因為n[2]是可變物件,是可以在地址中直接變更值的。
2. 深拷貝
深拷貝完全父物件與子物件。可使用copy模組的deepcopy()方法進行深拷貝,此外使用for迴圈複製可迭代序列也是深拷貝
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
列印結果可以看出,新舊物件本身的地址,和可變子物件地址都是不同的。
這裡看到m[0]和n1[0]的地址相同,但不可變物件的值變更地址就會變更,所以不會有問題。
三、 總結
綜上,咱們在實際應用中,如果拷貝物件的子物件都是不可變物件,那麼使用淺拷貝和深拷貝都行,
但如果待拷貝物件中有可變子物件,需要注意根據實際需求選擇使用深拷貝還是淺拷貝。