iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 10
0
自我挑戰組

30天學會Python系列 第 10

Python - 淺複製(shallow copy)與深複製(deep copy)

前言

可變與不可變型別
前面介紹list時提到在python中數據類型分為可變與不可變型別:

可變物件:該物件所指向記憶體中的值可以被改變
不可變物件:該物件所指向記憶體中的值不可以被改變,所以當變數指向的值改變時,等於將原來的值複製一份後存於一個新的地址,變數再指向這個新的地址

等號 = 賦值
在 list 中使用等號 = 賦值時,由於list為可變物件,實際行為是建立該list的引用(別名)

#%% list copy pass by reference
a = [1,2,3]
a_ref = a
a.append(4)
print("a: ", a)
print("a_ref: ", a)

由上述程式碼可知

  1. a, a_ref 是完全相同的
  2. a 和 a_ref 的記憶體位置相同
  3. 故 a_ref 只是 a 的引用
  4. 對 a 做修改,a_ref 也會被改變

由於a_ref為a的引用,指向的記憶體位置相同,所以在修改a_ref時也會一併修改到a,如果你的原意是想要一份獨立於a的複製,常常一個不小心沒注意產生bug! 程式寫到一半發現怎麼a_ref也被修改了卻找不出原因,吃了悶虧!這時候就需要談談list的淺複製與深複製了


淺複製與深複製 Shallow copy and deep copy

這個單元談到的淺複製與深複製也會受到這個性質的影響,簡單來說,淺與深的區別:

  • 淺複製僅複製容器中元素的地址
  • 深複製完全複製了一份副本,容器與容器中的元素地址都不一樣

一般 copy
三種方法

  • b = list(a)
  • b = a[:]
  • b = a.copy() PS: 淺複製Shallow copy
#%% list copy
a_list = list(a)
a_index = a[:]
a_copy = a.copy()
a.append(5)
print("Shallow copy")
print("a_list: ", a_list)
print("a_index: ", a_index)
print("a_copy: ", a_copy)

深複製 deep copy
需要import copy模組,裡面有deepcopy函式可以用

import copy
a = [1, [2,3]]
a_deepcopy = copy.deepcopy(a)

淺複製與深複製 Shallow copy and deep copy 的差別
淺複製與深複製的關鍵差別在於,複製的變數中是否有可變型別

#%% Shallow copy and deep copy
import copy
a = [1, [2,3]]
a_ref = a
a_shallowcopy = copy.copy(a)
a_deepcopy = copy.deepcopy(a)

print("{:<15}{:<20}{}".format("a:", f"{a}", f"id:{id(a_ref)}"))
print("{:<15}{:<20}{}".format("a_shallow_copy:", f"{a_shallowcopy}", f"id:{id(a_shallowcopy)}"))
print("{:<15}{:<20}{}".format("a_deepcopy:", f"{a_deepcopy}", f"id:{id(a_deepcopy)}"))

a[0] = 4
print("\nChange immutable part: a[0] = 4")
print("{:<15}{:<20}{}".format("a:", f"{a}", f"id:{id(a_ref)}"))
print("{:<15}{:<20}{}".format("a_shallow_copy:", f"{a_shallowcopy}", f"id:{id(a_shallowcopy)}"))
print("{:<15}{:<20}{}".format("a_deepcopy:", f"{a_deepcopy}", f"id:{id(a_deepcopy)}"))

其中 a[0] 為數字,即為不可變型別,則深/淺複製沒有差別
讓我們看看如果修改可變型別的內容會發生什麼事?接續上段程式碼:

a[1][1] = 5
print("\nChange mutable part: a[1][1] = 5")
print("{:<20}{:<20}{}".format("a:", f"{a}", f"id:{id(a_ref)}"))
print("{:<20}{:<20}{}".format("a_shallow_copy:", f"{a_shallowcopy}", f"id:{id(a_shallowcopy)}"))
print("{:<20}{:<20}{}".format("a_deepcopy:", f"{a_deepcopy}", f"id:{id(a_deepcopy)}"))

print("\nCheck variable id at deep level")
print("{:<20}{:<20}{}".format("a[1][1]:", f"{a[1][1]}", f"id:{id(a[1][1])}"))
print("{:<20}{:<20}{}".format("a_shallowcopy[1][1]:", f"{a_shallowcopy[1][1]}", f"id:{id(a_shallowcopy[1][1])}"))
print("{:<20}{:<20}{}".format("a_deepcopy[1][1]:", f"{a_deepcopy[1][1]}", f"id:{id(a_deepcopy[1][1])}"))

其中 a[1][1] 為list,即為可變型別,可以發現淺複製 (shallow copy) 被改變了,而深複製 (deep copy) 則沒有被改變,故得知:

  1. 淺/深複在製第一層變數均已指向不同記憶體
    BUT!!!
  2. 淺複製在第二層變數仍與原始變數指向相同記憶體
  3. 深複製在第二層變數已指向不同記憶體
  4. 深複製 (deep copy) 建立一份完全獨立的變數

Reference

Python中的淺複製(shallow copy)和深複製(deep copy)
https://www.itread01.com/content/1544614591.html


上一篇
Python - List Comprehesion
下一篇
Python - tuple 元組
系列文
30天學會Python30

尚未有邦友留言

立即登入留言