當前位置:
首頁 > 知識 > Python對象中的淺拷貝和深拷貝

Python對象中的淺拷貝和深拷貝

Python對象中的淺拷貝和深拷貝

Python部落(python.freelycode.com)組織翻譯,禁止轉載,歡迎轉發。

Python中的賦值語句不會創建對象的副本,而只是給對象綁定了新的名稱。對於不可變對象,這通常沒什麼區別。

但是在處理可變對象或可變對象集合時,你可能想找到一種方法來創建這些對象的「真正的副本」或「克隆」。

本質上來說就是有時候你會希望拷貝被修改時原對象不會自動修改。在這篇文章中,我將會告訴你如何在Python 3中拷貝或「克隆」對象,以及涉及的其他注意事項。

注意:本教程是用Python 3編寫的,但涉及到拷貝對象時,Python 2和3之間幾乎沒有區別。當有差異時,我會在文中指出它們。

我們先看看如何拷貝Python的內置集合。通過在現有集合上調用其工廠函數即可拷貝Python的內置可變集合(如列表,字典和集合):

Python對象中的淺拷貝和深拷貝

但是,此方法不適用於自定義對象,並且最重要的是,它僅創建淺拷貝。對於像列表,字典和集合這樣的複合對象,淺拷貝和深拷貝之間有一個重要區別:

  • 淺拷貝會構建一個新的集合對象,然後用原對象的子對象的引用填充它。實質上,淺拷貝只有一層。拷貝過程不會遞歸,因此不會創建子對象本身的副本。

  • 深拷貝會遞歸拷貝過程。這意味著會首先構造一個新的集合對象,然後遞歸地填充原始對象中的子對象的副本。以這種方式拷貝對象會遍歷整個對象樹,從而創建原始對象及其所有子對象的完全獨立的副本。

我知道這有點繞口,所以讓我們通過一些例子來深入了解淺拷貝和深拷貝的區別。

創建淺拷貝

在下面的例子中,我們將創建一個新的嵌套列表,然後用工廠函數list對它進行淺拷貝:

Python對象中的淺拷貝和深拷貝

這意味著ys現在將是一個新的獨立的對象,其內容與xs一致。你可以通過檢查這兩個對象來驗證這一點:

Python對象中的淺拷貝和深拷貝

為了確認ys真的是獨立於原對象,我們來設計一個小實驗。你可以嘗試添加一個新的子列表到原始對象(xs),然後檢查以確保此修改不會影響副本(ys):

Python對象中的淺拷貝和深拷貝

正如你所看到的,這具有預期的效果。在「表面」級別修改列表副本完全沒有問題。

但是,由於我們只創建了原始列表的淺拷貝,因此ys仍包含對存儲在xs中的原始子對象的引用。

這些子對象沒有被複制。他們只是在複製列表中再次引用。

因此,當你修改xs中一個子對象時,此修改也會在ys中反映出來——這是因為兩個列表共享相同的子對象。這種拷貝只是一個淺層,一層的拷貝:

Python對象中的淺拷貝和深拷貝

在上面的例子中,我們(似乎)只對xs做了一些改變。但事實證明,xs和ys索引為1的子列表都進行了修改。發生這種情況是因為我們只創建了原始列表的淺拷貝。

如果我們在第一步創建了xs的深拷貝,那麼這兩個對象將完全獨立。這是對象的淺層和深層拷貝之間的實際區別。

現在你知道了如何創建一些內置集合類的淺拷貝,並且也知道了淺拷貝和深拷貝之間的區別。我們還想知道的是:

  • 如何創建內置集合的深拷貝?

  • 如何創建任意對象的(淺和深)拷貝,包括自定義類?

這些問題的答案是Python標準庫中的copy模塊。該模塊提供了一個簡單的介面來創建任意Python對象的深淺拷貝。

創建深拷貝

讓我們重複前面的列表複製示例,但有一個重要的區別。這次我們將使用copy模塊中定義的deepcopy函數創建一個深拷貝:

Python對象中的淺拷貝和深拷貝

當你檢查xs和我們用copy.deepcopy創建的副本zs時,你會發現它們看起來都一樣——就像前面的例子一樣:

Python對象中的淺拷貝和深拷貝

但是,如果你對原始對象(xs)中的某個子對象進行了修改,則會看到此修改不會影響深拷貝(zs)。

這兩個對象,原始對象和副本,這次是完全獨立的。xs被遞歸拷貝,包括它的所有子對象:

Python對象中的淺拷貝和深拷貝

你可能需要花一些時間利用Python解釋器嘗試正確地使用這些示例。當你親身體驗這些例子後,你對拷貝的理解會更容易。

順便說一下,你還可以使用copy模塊中的函數創建淺拷貝。copy.copy函數會創建對象的淺拷貝。

如果你需要清楚地表明你正在代碼中某處創建淺拷貝,這非常有用。使用copy.copy可以讓你指出這一事實。但是,對於內置集合,只需使用列表,字典和集合的工廠函數來創建淺拷貝,這更有python的風格。

複製任意Python對象

我們仍然需要回答的問題是如何創建任意對象的(深淺)拷貝,包括自定義類。現在我們來看看。

copy模塊能再次幫我們。它的copy.copy和copy.deepcopy函數可用於複製任何對象。

同樣再次,理解如何使用這些的最好方法是通過一個簡單的實驗。依然以之前的列表複製示例為例。我們首先定義一個簡單的二維點類:

Python對象中的淺拷貝和深拷貝

我希望你們認可這很簡單。我添加了一個__repr__實現,以便我們可以輕鬆地在Python解釋器中檢查由此類創建的對象。

注意:上面的例子使用Python 3.6的f-string來構造__repr__返回的字元串。在Python 2和Python 3之前的版本中,你需要使用不同的字元串格式表達式,例如:

Python對象中的淺拷貝和深拷貝

接下來,我們將創建一個點實例,然後使用copy模塊進行淺拷貝:

Python對象中的淺拷貝和深拷貝

如果我們檢查原始點對象和它的(淺)拷貝的內容,我們會看到正如預期的那樣:

Python對象中的淺拷貝和深拷貝

要記住,由於我們的點對象使用原始類型(int)作為其坐標,因此在這種情況下,淺拷貝和深拷貝之間沒有區別。但我接下來會擴展這個例子。

我們來看一個更複雜的例子。我要定義另一個類來表示二維矩形。我將使對象的層次結構更複雜——我的矩形將使用點對象來表示它們的坐標:

Python對象中的淺拷貝和深拷貝

再次,我們首先嘗試創建一個矩形實例的淺拷貝:

Python對象中的淺拷貝和深拷貝

如果你檢查原始矩形及其副本,你會看到__repr__重寫進行良好,並且淺拷貝過程按預期工作:

Python對象中的淺拷貝和深拷貝

還記得前面的列表示例如何說明深和淺拷貝之間的區別嗎?我將在這裡使用相同的方法。我將在對象層次結構中的更深層修改一個對象,然後你會看到(淺層)拷貝中反映的此更改:

Python對象中的淺拷貝和深拷貝

我希望這和你預期的一致。接下來,我將創建原始矩形的深拷貝。然後我將進行另一個修改,你會看到哪些對象受到影響:

Python對象中的淺拷貝和深拷貝

瞧!這次深拷貝(drect)完全獨立於原始(rect)和淺拷貝(srect)。

我們已經在這裡介紹了很多內容,但關於複製對象還有一些細節。

在這個話題上深入研究是值得的,因此你可能需要研究copy模塊文檔。例如,通過定義特殊函數__copy__和__deepcopy__,對象可以控制它們如何被拷貝。

記住三件事
  • 創建對象的淺拷貝不會克隆子對象。因此,副本不完全獨立於原對象。

  • 對象的深層副本將遞歸地拷貝子對象。拷貝完全獨立於原始文件,但創建深拷貝較慢。

  • 可以使用copy模塊拷貝任意對象(包括自定義類)。

英文原文:https://realpython.com/blog/python/copying-python-objects/
譯者:β

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Python部落 的精彩文章:

用Python編輯視頻:MoviePy
快樂的遷移到 Python3

TAG:Python部落 |