1. 程式人生 > >1.Python淺複製和深複製——copy和deepcopy方法

1.Python淺複製和深複製——copy和deepcopy方法

問題:Python裡面如何拷貝一個物件?

參考:《Python Cookbook》

方法說明:

copy(x)

    Shallow copy operation on arbitrary Python objects.

    See the module's __doc__ string for more info. 

deepcopy(x, memo=None, _nil=[])

    Deep copy operation on arbitrary Python objects.

    See the module's __doc__ string for more info. 

標準庫中的copy模組提供了兩個方法來實現拷貝.一個方法是copy,它返回和引數包含內容一樣的物件.

import copy

new_list = copy.copy(existing_list)

有些時候,你希望物件中的屬性也被複制,可以使用deepcopy方法:

import copy

new_list_of_dicts = copy.deepcopy(existing_list_of_dicts)

當你對一個物件賦值的時候(做為引數傳遞,或者做為返回值),Python和Java一樣,總是傳遞原始物件的引用,而不是一個副本.其它一些語言當賦值的時候總是傳遞副本.Python從不猜測使用者的需求 ,如果你想要一個副本,你必須顯式的要求.

Python的行為很簡單,迅速,而且一致.然而,如果你需要一個物件拷貝而並沒有顯式的寫出來,會出現問題的,比如:

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

>>> b = a

>>> b.append(5)

>>> print a, b 

[1, 2, 3, 5] [1, 2, 3, 5]

在這裡,變數a和b都指向同一個物件(一個列表),所以,一旦你修改了二者之一,另外一個也會受到影響.無論怎樣,都會修改原來的物件.

注意:

要想成為一個Python高手,首先要注意的問題就是物件的變更操作和賦值,它們都是針對物件的引用操作的.一個語句比如a = []將a重新繫結給一個新物件,但不會影響以前的物件.然而,物件複製卻不同,當物件複製後,物件變更操作就有了區別. 

如 果你想修改一個物件,而且想讓原始的物件不受影響,那你就需要物件複製.正如本節說的一樣,你可以使用copy模組中的兩個方法來實現需求.一般的,可以 使用copy.copy,它可以進行物件的淺複製(shallow copy),它複製了物件,但對於物件中的元素,依然使用引用.

淺複製,有時無法獲得一個和原來物件完全一致的副本,如果你想修改物件中的元素,不僅僅是物件本身的話:

>>> list_of_lists = [ ['a'], [1, 2], ['z', 23] ]

>>> copy_lol = copy.copy(lists_of_lists) 

>>> copy_lol[1].append('boo') 

>>> print list_of_lists, copy_lol 

[['a'], [1, 2, 'boo'], ['z', 23]] [['a'], [1, 2, 'boo'], ['z', 23]] 

在這裡,變數list_of_lists,copy_lol指向了兩個不同的物件,所以我們可以修改它們任何一個, 而不影響另外一個.然而,如果我們修改了一個物件中的元素,那麼另一個也會受影響,因為它們中的元素還是共享引用.

如果你希望複製一個容器物件,以及它裡面的所有元素(包含元素的子元素),使用copy.deepcopy,這個方法會消耗一些時間和空間,不過,如果你需要完全複製,這是唯一的方法.

對於一般的淺拷貝,使用copy.copy就可以了,當然,你需要了解你要拷貝的物件.要複製列表L,使用list(L),要複製一個字典d,使用dict(d),要複製一個集合s,使用set(s),這樣,我們總結出一個規律,如果你要複製一個物件o,它屬於內建的型別t,那麼你可以使用t(o)來 獲得一個拷貝.dict也提供了一個複製版本,dict.copy,這個和dict(d)是一樣,我推薦你使用後者,這個使得程式碼更一致,而且還少幾個字元.

要複製一個別的型別,無論是你自己寫的還是使用庫中的,使用copy.copy,如果你自己寫一個類,沒有必要費神去寫clone和copy函式,如果你想定義自己的類複製的方式,實現一個__copy__,或者__getstate__和__setstate__.如果你想定義自己型別的deepcopy,實現方法__deepcopy__. 

注意你不用複製不可修改物件(string,數字,元組),因為你不用擔心修改它們.如果你想嘗試一下複製,依然會得到原來的.雖然無傷大雅,不過真的浪費盡力:

>>> s = 'cat' 

>>> t = copy.copy(s) 

>>> s is t 

True

is操作符用於不僅判斷兩個物件是否完全一致,而且是同一個物件(is判斷識別符號,要比較內容,使用==),判斷識別符號是否相等對於不可修改物件沒有什麼意義.然而 ,判斷識別符號對於可修改物件有時候是很重要的,比如,你不確定a和b是否指向同一個物件,使用a is b會立刻得到結果.這樣你可以自己判斷是否需要使用物件拷貝.

注意:

你可以使用另一種拷貝方式,給定一個列表L,無論是完整切片L[:]或者列表解析[x for x in L],都會獲得L的淺拷貝,試試L+[],L*1...但是上面兩種方法都會使人迷惑,使用list(L)最清晰和快速,當然,由於歷史原因,你可能會經常看到L[:]的寫法. 

對於dict,你可能見過下面的複製方法:

>>> for somekey in d: 

... d1[somekey] = d[somekey]

或者更簡單一些的方法,d1={},d1.update(d),無論怎樣,這些程式碼都是缺乏效率的,使用d1=dict(d)吧.

總結:

1.淺複製 :  使用copy.copy,它可以進行物件的淺複製(shallow copy),它複製了物件,但對於物件中的元素,依然使用引用.

2.深複製 :  使用copy.deepcopy,它可以進行深複製,不僅複製了物件,同時也複製了物件中的元素,但這需要犧牲一定的時間和空間。

3.要複製列表L,使用list(L),要複製一個字典d,使用dict(d),要複製一個集合s,使用set(s),這樣,我們總結出一個規律,如果你要複製一個物件o,它屬於內建的型別t,那麼你可以使用t(o)來 獲得一個拷貝.dict也提供了一個複製版本,dict.copy。