[ python ] 深淺拷貝
首先要了解什麼是拷貝、淺拷貝、深拷貝?
拷貝:
從原始資料複製一份出來,當複製成功後,這兩份資料都是相互獨立的,即修改任意一份資料都不會影響另一份資料。
淺拷貝:
python中,淺拷貝就是隻是拷貝最外層的型別,簡單來講就是拷貝了引用,並沒有拷貝內容. copy.copy()
深拷貝:
對於一個物件所有層次的拷貝(遞迴拷貝)copy.deepcopy()
要知道深淺拷貝的區別,首先要知道python中什麼是 可變資料型別 和 不可變資料型別
不可變資料型別的定義:
python中的不可變資料型別,不允許變數的值發生變化,如果改變了變數的值,相當於是新建了一個物件,而對於相同的值的物件,在記憶體中則只有一個物件,內部會有一個引用計數來記錄有多少個變數引用這個物件.
python中 不可變資料型別:
- 整型
- 浮點數
- 布林值
- 字串
- 元組
可變資料型別的定義:
可變資料型別,允許變數的值發生變化,即如果對變數進行append、+=等這種操作後,只是改變了變數的值,而不會新建一個物件,變數引用的物件的地址也不會變化,不過對於相同的值的不同物件,在記憶體中則會存在不同的物件,即每個物件都有自己的地址, 相當於記憶體中對於同值的物件儲存了多份,這裡不存在引用計數,是實實在在的物件。
python中 可變資料型別:
- 列表
- 字典
通過python中資料型別的分類,我們談論以下幾種的拷貝:
不可變資料型別:
- 賦值
- 淺拷貝
- 深拷貝
可變資料型別:
- 賦值
- 淺拷貝
- 深拷貝
不可變資料型別
1. 賦值
我們已知python中不可變資料型別:整型、浮點數、字串、布林值、元組
In [1]: a1 = 123 # 整型 In [2]: a2 = a1 In [3]: id(a1), id(a2) Out[3]: (1562980880, 1562980880) In [4]: b1 = 1.123 # 浮點數 In [5]: b2 = b1 In [6]: id(b1), id(b2) Out[6]: (1503028953024, 1503028953024) In [7]: c1 = 'hello' # 字串 In [8]: c2 = c1 In [9]: id(c1), id(c2) Out[9]: (1503040484272, 1503040484272) In [10]: d1 = True # 布林值 In [11]: d2 = d1 In [12]: id(d1), id(d2) Out[12]: (1562722720, 1562722720) In [13]: e1 = (1, 2, 3, 'hkey') # 元組 In [14]: e2 = e1 In [15]: id(e1), id(e2) Out[15]: (1503040349032, 1503040349032)
通過以上的例子,a1、a2 賦值的值是一樣的。因為python有一個重用機制,對於 不可變資料型別 來說,python並不會開闢一塊新的記憶體空間,而是維護同一塊記憶體地址,只是將 不可變資料型別 對應的地址引用賦值給變數a1、a2。所以根據輸出結果,a1和a2其實對應的是同一塊記憶體地址,只是兩個不同的引用。
結論:對於 不可變資料型別 通過'='賦值,不可變資料型別在記憶體當中用的都是同一塊地址。
2. 淺拷貝
In [1]: import copy In [2]: a1 = 3.14 In [3]: a2 = copy.copy(a1) In [4]: id(a1), id(a2) Out[4]: (1690132070168, 1690132070168)
通過使用copy模組裡的copy()函式來進行淺拷貝,把a1拷貝一份賦值給a2,檢視輸出結果發現,a1和a2的記憶體地址還是一樣。
結論:對於 不可變資料型別 通過淺拷貝,不可變資料型別在記憶體當中用的都是同一塊地址。
3. 深拷貝
In [1]: import copy In [2]: a1 = 'hello' In [3]: a2 = copy.deepcopy(a1) In [4]: id(a1), id(a2) Out[4]: (1645307287064, 1645307287064)
通過使用copy模組裡的deepcopy()函式來進行深拷貝,把a1拷貝一份賦值給a2,檢視輸出結果發現,a1和a2的記憶體地址還是一樣。
結論:對於 不可變資料型別 通過深拷貝,不可變資料型別在記憶體當中用的都是同一塊地址。
這裡可以對 不可變資料型別 下一個定義了:
對於python中 不可變資料型別 的賦值、淺拷貝、深拷貝在記憶體中都是指向同一記憶體地址。如下圖:
可變資料型別
1. 賦值
In [1]: n1 = {"k1": "wu", "k2": 123, "k3": ["alex", 678]} In [2]: n2 = n1 In [3]: id(n1), id(n2) Out[3]: (1888346752712, 1888346752712)
在上面的例子中,我們使用了列表巢狀列表的方式,賦值後記憶體空間地址是一致的。其中原理如下圖:
結論:對於 可變資料型別 進行賦值記憶體地址是不會變化的。
2. 淺拷貝
import copy n1 = {"k1": "wu", "k2": 123, "k3": ["alex", 678]} n3 = copy.copy(n1) # 淺拷貝 print("第一層字典的記憶體地址:") print(id(n1)) print(id(n3)) print("第二層巢狀的列表的記憶體地址:") print(id(n1["k3"])) print(id(n3["k3"])) 執行結果: 第一層字典的記憶體地址: 2260623665800 2260625794440 第二層巢狀的列表的記憶體地址: 2260626131144 2260626131144
通過以上結果可以看出,進行淺拷貝時,我們的字典第一層n1和n3指向的記憶體地址已經改變了,但是對於第二層裡的列表並沒有拷貝,它的記憶體地址還是一樣的。原理如下圖:
結論:對於 python 可變資料型別,淺拷貝只能拷貝第一層地址。
3. 深拷貝
import copy n1 = {"k1": "wu", "k2": 123, "k3": ["alex", 678]} n4 = copy.deepcopy(n1) # 深拷貝 print("第一層字典的記憶體地址:") print(id(n1)) print(id(n4)) print("第二層巢狀的列表的記憶體地址:") print(id(n1["k3"])) print(id(n4["k3"])) 執行結果: 第一層字典的記憶體地址: 2018569398920 2018574527176 第二層巢狀的列表的記憶體地址: 2018576058568 2018576056840
通過以上結果發現,進行深拷貝時,字典裡面的第一層和裡面巢狀的地址都已經變了。對於深拷貝,它會拷貝多層,將第二層的列表也拷貝一份,
如果還有第三層巢狀,那麼第三層的也會拷貝,但是對於裡面的最小元素,比如數字和字串,這裡就是“wu”,123,“alex”,678之類的,
按照python的機制,它們會共同指向同一個位置,它的記憶體地址是不會變的。原理如下圖:
結論:對於 python 可變資料型別,它裡面巢狀多少層,就會拷貝多少層出來,但是最底層的數字和字串不變。
python 深淺拷貝的例項:
我們在維護伺服器資訊的時候,經常會要更新伺服器資訊,這時我們重新一個一個新增是比較麻煩的,我們可以把原資料型別拷貝一份,在它的基礎上做修改。
例項1:使用淺拷貝
import copy # 定義了一個字典,儲存伺服器資訊 dic = { 'cpu':[80, ], 'mem':[80, ], 'disk':[80, ] } print('before', dic) new_dic = copy.copy(dic) new_dic['cpu'][0] = 50 # 更新 cpu 為 50 print(dic) print(new_dic) 執行結果: before {'cpu': [80], 'mem': [80], 'disk': [80]} {'cpu': [50], 'mem': [80], 'disk': [80]} {'cpu': [50], 'mem': [80], 'disk': [80]}
這時我們會發現,使用淺拷貝時,我們修改新的字典的值之後,原來的字典裡面的cpu值也被修改了,這並不是我們希望看到的。
例項2:使用深拷貝
import copy # 定義了一個字典,儲存伺服器資訊 dic = { 'cpu':[80, ], 'mem':[80, ], 'disk':[80, ] } print('before', dic) new_dic = copy.deepcopy(dic) new_dic['cpu'][0] = 50 # 更新 cpu 為 50 print(dic) print(new_dic) 執行結果: before {'cpu': [80], 'disk': [80], 'mem': [80]} {'cpu': [80], 'disk': [80], 'mem': [80]} {'cpu': [50], 'disk': [80], 'mem': [80]}
使用深拷貝的時候,發現只有新的字典的cpu值被修改了,原來的字典裡面的cpu值沒有變。大功告成!