Python 中的賦值語句不會對對象進行拷貝,僅僅是將變量名指向對象。對於不可修改的對象來說,這種機制不會影響我們日常的使用。但是,對於可修改的對象,你偶爾可能需要對該對象做一個真正的複製。何為真正的複製?就是修改拷貝來的對象不會影響原來的對象。
Python 中內置的可修改的集合類對象,比如列表、字典、集合等,可以直接使用對應的工廠方法進行拷貝。
需要注意的是,對於複合類型的對象,比如列表、字典、集合等,複製有淺複製與深複製兩種類型。
淺複製意味著新建一個對象,但是其子元素仍然指向的對應原對象的子對象。也就是說,這只會對原對象進行一層的拷貝,而不會遞歸的對子對象也進行拷貝。
深複製則會遞歸的對子對象進行拷貝。
如果上面的看不懂,沒關係,我們通過一個個例子來搞清楚。
淺複製
現在下面這個列表,我們使用 list() 方法對它進行複製
這意味著 xs 和 ys 是兩個不同的對象。我們可以看看它們的值
為了證明 xs 與 ys 確實不同,我們可以做個小實驗。添加一個子列表到 xs 中,然後看會不會影響 ys 。
與預期相同,如果只是修改原對象這一層,確實不對對 ys 造成影響。
但是由於是淺複製,ys 中的子元素並非是 xs 子元素的拷貝,僅僅是 xs 子對象的引用。因此,如果你修改 xs 中的子元素,ys 中的子元素同樣會被修改。
看起來我們只是修改了 xs 中的子元素,實則 ys 中的子元素也被修改了,這是由於淺複製的緣故。
現在我們大概清楚了淺複製與深複製的區別,但是還有兩個問題待解決:
一是,對於內置的集合類對象,我們怎麼進行深複製?
二是,對於任意的類,又該如何進行淺複製與深複製?
答案是標準庫中的 copy 模塊,其提供了簡單的方法對對象進行淺複製或深複製。
深複製
這次我們仍然使用上面的例子,不同的是,我們使用 copy 模塊中的 deepcopy() 方法來進行複製。
還是先看看各自的值
這次,因為 zs 對 xs 進行了深複製,即不僅拷貝了 xs 本身,還對它的子對象都進行了遞歸的拷貝,所以,如果我們再次修改 xs 的子元素時,並不會對 zs 造成影響。我們來試一試
果然,與預期一致。
順便說下,使用 copy 模塊中的 copy() 方法可以對對象進行淺複製。平時開發需要淺複製的時候,你可以使用該方法,但如果碰到內置的集合類對象,比如列表、字典、集合等的時候,使用對應的工廠方法如 list() ,dict() ,set() 等更 Pythonic 一些。
對任意對象進行複製
現在有一個點類 Point ,如下
其中,__repr__ 方法可以讓我們更方便的查看對象的信息。需要注意的是,f'...' 這種格式化字符串的方式在 Python 3.6 才支持,如果是 Python 2 或者 3.6 以前的版本可以使用 '...' % (v1[, v2, ...]) 的方式。
現在我們創建一個點對象並進行淺複製
查看兩個對象的信息
和預期一致。
需要注意的是,因為類的兩個成員 x 和 y 都是簡單類型,這裡是整型,所以這裡淺複製與深複製沒有任何差別。後面我會擴展這個例子。
現在我要定義一個矩形類,其類成員將會使用到上面的點類。
首先我們先試一下淺複製
看下各自的信息
記得我們上面修改列表的淺複製與深複製的例子嗎?這裡,我要類似的實驗。我們修改 rect 的成員,不出意外的話,srect 也會改變。
果然。
下面,我會進行深複製,然後再類似的修改
這次,深複製出來的 drect 對象與 srect 才能說是「真正不同」的對象。
copy 模塊中還有其它很多用法,比如定義對象的 __copy__() 和 __deepcopy__() 方法可以自定義對象的淺複製與深複製行為等。這不是本文的重心,有興趣的可以去看相應的文檔 https://docs.python.org/3/library/copy.html 。
小結
* 淺複製不會克隆子對象,所以,複製出來的對象和原對象並非完全不想關。
* 深複製會遞歸的克隆子對象,所以,複製出來的對象和原對象完全不相關,但是深複製比淺複製會慢一些。
* 使用 copy 模塊你可以複製任何類,不管是淺複製還是深複製。
鞏固
從網上找了組圖片(侵刪),讓大家加深下認識
首先是賦值
然後是淺複製
最後是深複製