淺顯直白的Python深複製與淺複製區別說明

菜鳥阿喬發表於2023-02-22

 

一、可變資料型別與不可變資料型別

  在開始說深複製與淺複製前,我們先來弄清楚,可變物件與不可變物件

  總的來說,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]的地址相同,但不可變物件的值變更地址就會變更,所以不會有問題。

 

三、 總結

  綜上,我們們在實際應用中,如果複製物件的子物件都是不可變物件,那麼使用淺複製和深複製都行,

  但如果待複製物件中有可變子物件,需要注意根據實際需求選擇使用深複製還是淺複製。

相關文章