Python中的深拷貝和淺拷貝(蠻有意思,仔細讀會發現很多內容)

Python中的對象之間賦值時是按引用傳遞的,如果需要拷貝對象,需要使用標準庫中的copy模塊。

1、copy.copy 淺拷貝 只拷貝父對象,不會拷貝對象的內部的子對象

2、copy.deepcopy 深拷貝 拷貝對象及其子對象

>>> import copy
>>> a = [1,2,3,4,['a','b']]  #原始對象

>>> b = a  #賦值,傳對象的引用

>>> c = copy.copy(a)

>>> d = copy.deepcopy(a)

>>> a.append(5)
>>> a[4].append('c')

>>> print 'a=',a
a= [1, 2, 3, 4, ['a', 'b', 'c'], 5]
>>> print 'b=',b
b= [1, 2, 3, 4, ['a', 'b', 'c'], 5]
>>> print 'c=',c
c= [1, 2, 3, 4, ['a', 'b', 'c']]
>>> print 'd=',d
d= [1, 2, 3, 4, ['a', 'b']]

之前關於 Python 的作用域、賦值、參數傳遞,我們接連談了幾篇文章:

今天我們依然要就相關話題繼續下去。

首先是上次最後的思考題:



m = [1, 2, [3]]
n = m[:]
n[1] = 4
n[2][0] = 5
print(m)

m 的結果是什麼?

正確答案是 [1, 2, [5]] ,這次比上次好點,有 35% 的正確率。

當時我留了個提示,說和淺拷貝、深拷貝有關,現在我們就來具體說一說。

假設有這樣一個 list 變量 m,其中有 4 個元素(別被嵌套迷惑了):



m = [1, 2, [3, 4], [5, [6, 7]]]

爲了更直觀的表示,我來畫個圖:

現在我們想要再來“複製”一個同樣的變量。也許第一個閃過腦中的念頭就是:



n = m

但看了前面的文章後你應該知道,這樣的 賦值只相當於增加了一個標籤,並沒有新的對象產生 :

用 id 驗證下就知道, m 和 n 仍然是同一個東西 。那麼他們內部的元素自然也是一樣的,對其中一個進行修改,另一個也會跟着變:



m = [1, 2, [3, 4], [5, [6, 7]]]
print('m:', id(m))
print([id(i) for i in m])
n = m
print('n:', id(n))
print([id(i) for i in n])
print(n is m)
print(n[0] is m[0])
print(n[2] is m[2])
n[0] = -1
print(m)
n[2][1] = -1
print(m)

輸出



m: 4564554888
[4556507504, 4556507536, 4564554760, 4564555016]
n: 4564554888
[4556507504, 4556507536, 4564554760, 4564555016]
True
True
True
[-1, 2, [3, 4], [5, [6, 7]]]
[-1, 2, [3, -1], [5, [6, 7]]]

因此有人將此操作稱爲“ 舊瓶裝舊酒 ”,只是多貼了一層標籤,這不能達到我們的目的。要得到一個對象的“拷貝”,我們需要用到 copy 方法:



from copy import copy
m = [1, 2, [3, 4], [5, [6, 7]]]
print('m:', id(m))
print([id(i) for i in m])
n = copy(m)
print('n:', id(n))
print([id(i) for i in n])
print(n is m)
print(n[0] is m[0])
print(n[2] is m[2])
n[0] = -1
print(m)
n[2][1] = -1
print(m)

輸出



m: 4340253832
[4333009264, 4333009296, 4340253704, 4340253960]
n: 4340268104
[4333009264, 4333009296, 4340253704, 4340253960]
False
True
True
[1, 2, [3, 4], [5, [6, 7]]]
[1, 2, [3, -1], [5, [6, 7]]]

從結果中可以看出, n 和 m 已不是同一個對象 ,對於某個元素的重新賦值不會影響原對象。但是,它們 內部的元素全都是一樣的 ,所以對一個可變類型元素的修改,則仍然會反應在原對象中。

(其實這裏1、2也是指向同一個對象,但作爲不可變對象來說,它們互不影響,直觀上的感受就相當於是複製了一份,故簡化如圖上所示)

這種複製方法叫做 淺拷貝 ( shallow copy ),又被人形象地稱作“ 新瓶裝舊酒 ”,雖然產生了新對象,但裏面的內容還是來自同一份。

如果要徹底地產生一個和原對象完全獨立的複製品,得使用 深拷貝 ( deep copy ):



from copy import deepcopy
m = [1, 2, [3, 4], [5, [6, 7]]]
print('m:', id(m))
print([id(i) for i in m])
n = deepcopy(m)
print('n:', id(n))
print([id(i) for i in n])
print(n is m)
print(n[0] is m[0])
print(n[2] is m[2])
n[0] = -1
print(m)
n[2][1] = -1
print(m)

輸出



m: 4389131400
[4381886832, 4381886864, 4389131272, 4389131528]
n: 4389131208
[4381886832, 4381886864, 4389131656, 4389145736]
False
True
False
[1, 2, [3, 4], [5, [6, 7]]]
[1, 2, [3, 4], [5, [6, 7]]]

此時, 對新對象中元素做任何改動都不會影響原對象 。新對象中的子列表,無論有多少層,都是新的對象,有不同的地址。

按照前面的比喻,深拷貝就是“ 新瓶裝新酒 ”。

你可能會注意到一個細節:n 中的前兩個元素的地址仍然和 m 中一樣。這是由於它們是 不可變對象,不存在被修改的可能,所以拷貝和賦值是一樣的 。

於是,深拷貝也可以理解爲,不僅是對象自身的拷貝,而且對於對象中的每一個子元素,也都進行同樣的拷貝操作。這是一種 遞歸 的思想。

不過額外要說提醒一下的是, 深拷貝的實現過程並不是完全的遞歸 ,否則如果對象的某級子元素是它自身的話,這個過程就死循環了。實際上, 如果遇到已經處理過的對象,就會直接使用其引用,而不再重複處理 。聽上去有點難懂是不是?想想這個例子大概就會理解了:



from copy import deepcopy
m = [1, 2]
m.append(m)
print(m, id(m), id(m[2]))
n = deepcopy(m)
print(n, id(n), id(n[2]))

輸出



[1, 2, [...]] 4479589576 4479589576
[1, 2, [...]] 4479575048 4479575048

最後,還是給各位留個思考:



from copy import deepcopy
a = [3, 4]
m = [1, 2, a, [5, a]]
n = deepcopy(m)
n[3][1][0] = -1
print(n)

深拷貝後的 n,修改了其中一個元素值,會是怎樣的效果?

思考一下輸出會是什麼?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章