1. 程式人生 > >Python中的淺複製(shallow copy)和深複製(deep copy)

Python中的淺複製(shallow copy)和深複製(deep copy)


近期雜事太多,部落格一直沒更新,9月最後一天了,總得寫點吧

今天記一下以前碰到過,最近又碰到的問題:python的深複製和淺複製
神奇的python中,copy竟然還有兩種,一深一淺(emm),來看看它們有什麼區別。

python值管理方式

這本質原因是python採用的是基於值的管理方式,
而C語言中,系統會為每個變數分配記憶體空間,當改變變數的值時,改變的是記憶體空間中的值,變數的地址是不改變的。
基於值的管理方式是什麼意思呢? 來看一個例子:

a = [1,2]
b = a
print ('a: ', a, 'id(a): ', id(a))
print ('b: ', b, 'id(a): ', id(b))

輸出:
('a: ', [1, 2], 'id(a): ', 4496519824)
('b: ', [1, 2], 'id(a): ', 4496519824)

可以發現a和b的記憶體地址相同,也就是說,a和b是完全相同的,b只是a的一個引用(也就是一個別名)
例如,帝都=北京,帝都就指的是北京這座城市,是完全相同的物理存在。這就是python的基於值的管理。

當我們採用“=”對引數進行賦值的時候,其實就是一個引用,若對引用進行了修改,原始引數也將被改變,這是我們在開發過程中很容易忽略的,也會導致我們的資料被意外修改,導致奇怪的bug。
請看例子:

a = [1,2]
b = a
b.append(3)
print ('a: ', a, 'id(a): ', id(a))
print ('b: ', b, 'id(a): ', id(b))

輸出:
('a: ', [1, 2, 3], 'id(a): ', 4335018640)
('b: ', [1, 2, 3], 'id(a): ', 4335018640)

可以看到,對b進行了增加一個元素,同時地,a也被修改了。

通過以上兩個小例子可以瞭解python值的管理方式,以及知道"="賦值的本質。
那麼當我們想複製出一個完全獨立於原始變數的變數,應該怎麼做呢?
這就需要淺複製(shallow copy) 和 深複製(deep copy)。

深複製與淺複製的使用及區別

在模組copy中有deepcopy方法用於深複製,copy方法用於淺複製。
深複製和淺複製到區別,在於複製的物件中是否有可變型別,aha?,可變型別是什麼?
簡單複習一下,python中

可變型別有:列表,字典
不可變型別有:數字,字串,元組


為了循序漸進,這裡分兩個例子講解

例子1:

a = [1, [2, 3]]
a_equal = a
a_shallowcopy = copy.copy(a)
a_deepcopy = copy.deepcopy(a)
a[0] = 4    # 改變a中的第一個元素,即改變a中的數字
print'a:               ', a,       'id(a):             ', id(a)
print'a_equal:         ', a_equal, 'id(a_equal):       ', id(a_equal)
print'a_shallowcopy:   ', a_shallowcopy, 'id(a_shallowcopy): ', id(a_shallowcopy)
print'a_deepcopy:      ', a_deepcopy, 'id(a_deepcopy):    ', id(a_deepcopy)

輸出:
a: [4, [2, 3]] id(a): 4578314360
a_equal: [4, [2, 3]] id(a_equal): 4578314360
a_shallowcopy: [1, [2, 3]] id(a_shallowcopy): 4562436248
a_deepcopy: [1, [2, 3]] id(a_deepcopy): 4562362096

可以發現:
=賦值: 對於修改,受到影響,地址相同
淺複製:對於修改,不受影響,地址不相同
深複製:對於修改,不受影響,地址不相同

這麼一看,深複製和淺複製完全一樣啊,哪裡有區別?
還記得上面說的可變型別嗎? 剛一看剛剛改變list a當中的第一個元素,是一個數字吧。
數字是不可變型別,所以我們在修改不可變型別時,深複製和淺複製沒有區別。
那麼當我們改變a當中第二個元素,即list呢,會怎麼樣,請看例子2:

a = [1, [2, 3]]
a_equal = a
a_shallowcopy = copy.copy(a)
a_deepcopy = copy.deepcopy(a)
a[0] = 4    # 改變a中的第一個元素,即改變a中的數字
a[1][1] = 5     # 改變a當中第二個元素中的元素,即 改變a當中的list
print'a:               ', a,       'id(a):             ', id(a)
print'a_equal:         ', a_equal, 'id(a_equal):       ', id(a_equal)
print'a_shallowcopy:   ', a_shallowcopy, 'id(a_shallowcopy): ', id(a_shallowcopy)
print'a_deepcopy:      ', a_deepcopy, 'id(a_deepcopy):    ', id(a_deepcopy)

輸出:
a: [4, [2, 5]] id(a): 4454672504
a_equal: [4, [2, 5]] id(a_equal): 4454672504
a_shallowcopy: [1, [2, 5]] id(a_shallowcopy): 4438794392
a_deepcopy: [1, [2, 3]] id(a_deepcopy): 4438720240

可以發現:
=賦值: 對於修改,受到影響,地址相同
淺複製:對於不變型別的修改,不受影響;對於可變型別的修改,受影響,地址不相同
深複製:對於不變型別的修改,不受影響;對於可變型別的修改,不受影響,地址不相同

為了不必要的麻煩,我們想要一個完全獨立的變數,要一個與原始變數解耦的變數,那請使用深複製吧。

還可以從另外一個角度理解深複製和淺複製的區別,那麼就是
淺複製僅複製父物件,不會複製物件內部的子物件(如例子2當中的a的第二個元素[2,3])
深複製不僅複製父物件,而且還複製內部的子物件。

什麼是子物件呢? 那就是可變型別,所以還是回到了開頭提到的,深複製和淺複製的區別,就要看變數中是否含有可變物件,哈哈。