1. 程式人生 > >[ python ] 深淺拷貝

[ 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值沒有變。大功告成!