2024年7月3日 星期三

Python 學習筆記 : 用 copy.deepcopy() 深度複製物件

今天在 2018 年買於明儀的 "Python 金融分析" 這本書的第 4 章看到 copy.deepcopy() 函式的用法 (我最近都在消化 2018 那年買的一堆舊書, 哈), 以前都沒機會用, 就隨手做了一下測試. 

Pyton 的變數其實都是一個記憶體參考, 尤其容器物件可以裝載各種其他物件, 放進容器中的其實也是參考, 例如下面的串列 a, 我們複製了 3 個 a 當作元素來組成 b 串列 : 

>>> a=[1, 2, 3]  
>>> b=[a, a, a]   
>>>  
[[1, 2, 3], [1, 2, 3], [1, 2, 3]]

用 is 運算子檢驗, b 地三個元素之記憶體位址與原始物件 a 的記憶體位址是相同的, 即 b[0], b[1], b[2] 都指向 a 的位址 :

>>> b[0] is a     
True
>>> b[1] is a   
True
>>> b[2] is a     
True

用 id() 函式查詢記憶體位址可知確實都是相同位址 : 

>>> id(a)  
1885677182592
>>> id(b[0])    
1885677182592
>>> id(b[1])    
1885677182592
>>> id(b[2])    
1885677182592

所以 b 裡面的元素都是指向原始物件 a 的參考而已, 並非複製了三份 a 作為元素, 因此它們彼此是連動的 (都指向同樣的記憶體位址), 如果修改其中任一個, 其他變數也會被改變, 例如 :

>>> a[0]=999   
>>> b   
[[999, 2, 3], [999, 2, 3], [999, 2, 3]]   
>>> b[2][2]=7   
>>> a      
[999, 2, 7]   
>>> b   
[[999, 2, 7], [999, 2, 7], [999, 2, 7]]

如果要避免這種因為共同指向原始物件的連動關係, 則可用 copy.deepcopy() 函式, 它會執行物件的深度複製, 亦即複製內容到另一段記憶體內 :

>>> import copy   
>>> dir(copy)    
['Error', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', '_copy_dispatch', '_copy_immutable', '_deepcopy_atomic', '_deepcopy_dict', '_deepcopy_dispatch', '_deepcopy_list', '_deepcopy_method', '_deepcopy_tuple', '_keep_alive', '_reconstruct', 'copy', 'deepcopy', 'dispatch_table', 'error']

使用時可從 copy 複製 deepcopy() 函式 :

>>> from copy import deepcopy   
>>> c=[deepcopy(a), deepcopy(a), deepcopy(a)]     # 也可以用 3*[deepcopy(a), ] 
>>> c    
[[999, 2, 7], [999, 2, 7], [999, 2, 7]]   
>>> id(c[0])   
1885683609600
>>> id(c[1])   
1885675886592
>>> id(c[2])   
1885682836992
>>> id(a)   
1885677182592

深度複製是將內容拷貝一份到另一段記憶體上儲存, 所以 c 串列的三個元素位址都不同, 也與 a 不同, 因此更改內容也只影響自己, 不會連動 :

>>> c[1][1]=888
>>> c   
[[999, 2, 7], [999, 888, 7], [999, 2, 7]]
>>> a   
[999, 2, 7]

沒有留言 :