python中的shallow copy 與 deep copy

来源:http://www.cnblogs.com/wulaa/archive/2017/11/25/7896201.html
-Advertisement-
Play Games

今天在寫代碼的時候遇到一個奇葩的問題,問題描述如下: 代碼中聲明瞭一個list,將list作為參數傳入了function1()中,在function1()中對list進行了del()即刪除了一個元素。 而function2()也把list作為參數傳入使用,在調用完function1()之後再調用fu ...


今天在寫代碼的時候遇到一個奇葩的問題,問題描述如下:

代碼中聲明瞭一個list,將list作為參數傳入了function1()中,在function1()中對list進行了del()即刪除了一個元素。

而function2()也把list作為參數傳入使用,在調用完function1()之後再調用function2()就出現了問題,list中的值已經被改變了,就出現了bug。

直接上代碼:

list = [0, 1, 2, 3, 4, 5]


def function1(list):
    del list[1]
    print(list)


def function2(list):
    print(list)


function1(list)
function2(list)

我並不希望function2()中的list改變,查了一下解決辦法說是可對list進行copy:

newList = list.copy()
function2(newList)

 

在查解決辦法的過程中發現了還有一個方法叫做deepcopy(),那麼問題來了,deepcopy()與copy()的區別是什麼?

先點到源碼里看了下源碼,發現有註釋,很開心。註釋如下:

"""Generic (shallow and deep) copying operations.

Interface summary:

        import copy

        x = copy.copy(y)        # make a shallow copy of y
        x = copy.deepcopy(y)    # make a deep copy of y

For module specific errors, copy.Error is raised.

The difference between shallow and deep copying is only relevant for
compound objects (objects that contain other objects, like lists or
class instances).

- A shallow copy constructs a new compound object and then (to the
  extent possible) inserts *the same objects* into it that the
  original contains.

- A deep copy constructs a new compound object and then, recursively,
  inserts *copies* into it of the objects found in the original.

Two problems often exist with deep copy operations that don't exist
with shallow copy operations:

 a) recursive objects (compound objects that, directly or indirectly,
    contain a reference to themselves) may cause a recursive loop

 b) because deep copy copies *everything* it may copy too much, e.g.
    administrative data structures that should be shared even between
    copies

Python's deep copy operation avoids these problems by:

 a) keeping a table of objects already copied during the current
    copying pass

 b) letting user-defined classes override the copying operation or the
    set of components copied

This version does not copy types like module, class, function, method,
nor stack trace, stack frame, nor file, socket, window, nor array, nor
any similar types.

Classes can use the same interfaces to control copying that they use
to control pickling: they can define methods called __getinitargs__(),
__getstate__() and __setstate__().  See the documentation for module
"pickle" for information on these methods.
"""

然而看了看,一臉懵逼。還是百度繼續查資料吧:

https://iaman.actor/blog/2016/04/17/copy-in-python大佬總結的很好。

copy其實就是shallow copy,與之相對的是deep copy

結論:

1.對於簡單的object,shallow copy和deep copy沒什麼區別

>>> import copy
>>> origin = 1
>>> cop1 = copy.copy(origin) 
#cop1 是 origin 的shallow copy
>>> cop2 = copy.deepcopy(origin) 
#cop2 是 origin 的 deep copy
>>> origin = 2
>>> origin
2
>>> cop1
1
>>> cop2
1
#cop1 和 cop2 都不會隨著 origin 改變自己的值
>>> cop1 == cop2
True
>>> cop1 is cop2
True

 

2.複雜的 object, 如 list 中套著 list 的情況,shallow copy 中的子list,並未從原 object 真的「獨立」出來。

如果你改變原 object 的子 list 中的一個元素,你的 copy 就會跟著一起變。這跟我們直覺上對「複製」的理解不同。

>>> import copy
>>> origin = [1, 2, [3, 4]]
#origin 裡邊有三個元素:1, 2,[3, 4]
>>> cop1 = copy.copy(origin)
>>> cop2 = copy.deepcopy(origin)
>>> cop1 == cop2
True
>>> cop1 is cop2
False 
#cop1 和 cop2 看上去相同,但已不再是同一個object
>>> origin[2][0] = "hey!" 
>>> origin
[1, 2, ['hey!', 4]]
>>> cop1
[1, 2, ['hey!', 4]]
>>> cop2
[1, 2, [3, 4]]
#把origin內的子list [3, 4] 改掉了一個元素,觀察 cop1 和 cop2

 cop1,也就是shallow copy 跟著 origin 改變了。而 cop2 ,也就是 deep copy 並沒有變。

那麼問題又來了,有deepcopy直接用就好了為啥還要有copy?

 這個問題的解決要從python變數存儲的方法說起,在python中,與其說是把值賦給了變數,不如說是給變數建立了一個到具體值的reference(引用)

>>> a = [1, 2, 3]
>>> b = a
>>> a = [4, 5, 6] //賦新的值給 a
>>> a
[4, 5, 6]
>>> b
[1, 2, 3]
# a 的值改變後,b 並沒有隨著 a 變

>>> a = [1, 2, 3]
>>> b = a
>>> a[0], a[1], a[2] = 4, 5, 6 //改變原來 list 中的元素
>>> a
[4, 5, 6]
>>> b
[4, 5, 6]
# a 的值改變後,b 隨著 a 變了

 

 上面代碼,都改變了a的值,不同的是:第一段是給a賦新值,第二段是直接改變了list中的元素。

下麵解釋下這詭異的現象:

首次把 [1, 2, 3] 看成一個物品。a = [1, 2, 3] 就相當於給這個物品上貼上 a 這個標簽。而 b = a 就是給這個物品又貼上了一個 b的標簽。

 

第一種情況:

a = [4, 5, 6] 就相當於把 a 標簽從 [1 ,2, 3] 上撕下來,貼到了 [4, 5, 6] 上。

在這個過程中,[1, 2, 3] 這個物品並沒有消失。 b 自始至終都好好的貼在 [1, 2, 3] 上,既然這個 reference 也沒有改變過。 b 的值自然不變。

 

第二種情況:

a[0], a[1], a[2] = 4, 5, 6 則是直接改變了 [1, 2, 3] 這個物品本身。把它內部的每一部分都重新改裝了一下。內部改裝完畢後,[1, 2, 3] 本身變成了 [4, 5, 6]

而在此過程當中,a 和 b 都沒有動,他們還貼在那個物品上。因此自然 a b 的值都變成了 [4, 5, 6]

 

用copy.copy()。結果卻發現本體與 copy 之間並不是獨立的。有的時候改變其中一個,另一個也會跟著改變。也就是本文一開頭提到的例子:

>>> import copy
>>> origin = [1, 2, [3, 4]]
#origin 裡邊有三個元素:1, 2,[3, 4]
>>> cop1 = copy.copy(origin)
>>> cop2 = copy.deepcopy(origin)
>>> cop1 == cop2
True
>>> cop1 is cop2
False 
#cop1 和 cop2 看上去相同,但已不再是同一個object
>>> origin[2][0] = "hey!" 
>>> origin
[1, 2, ['hey!', 4]]
>>> cop1
[1, 2, ['hey!', 4]]
>>> cop2
[1, 2, [3, 4]]
#把origin內的子list [3, 4] 改掉了一個元素,觀察 cop1 和 cop2

官方解釋:

The difference between shallow and deep copying is only relevant for compound objects (objects that contain other objects, like lists or class instances):

A shallow copy constructs a new compound object and then (to the extent possible) inserts references into it to the objects found in the original.

A deep copy constructs a new compound object and then, recursively, inserts copies into it of the objects found in the original.

兩種 copy 只在面對複雜對象時有區別,所謂複雜對象,是指對象中含其他對象(如複雜的 list 和 class)。

由 shallow copy 建立的新複雜對象中,每個子對象,都只是指向自己在原來本體中對應的子對象。而 deep copy 建立的複雜對象中,存儲的則是本體中子對象的 copy,並且會層層如此 copy 到底。

先看這裡的 shallow copy。 如圖所示,cop1 就是給當時的 origin 建立了一個鏡像。origin 當中的元素指向哪, cop1 中的元素就也指向哪。這就是官方 doc 中所說的 inserts references into it to the objects found in the original 。

這裡的關鍵在於,origin[2],也就是 [3, 4] 這個 list。根據 shallow copy 的定義,在 cop1[2] 指向的是同一個 list [3, 4]。那麼,如果這裡我們改變了這個 list,就會導致 origin 和 cop1 同時改變。這就是為什麼上邊 origin[2][0] = "hey!" 之後,cop1 也隨之變成了 [1, 2, ['hey!', 4]]

 

再來看 deep copy。 從圖中可以看出,cop2 是把 origin 每層都 copy 了一份存儲起來。這時候的 origin[2] 和 cop2[2] 雖然值都等於 [3, 4],但已經不是同一個 list了。

 

既然完全獨立,那無論如何改變其中一個,另一個自然不會隨之改變。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ecto 簡介 ecto 相當於 elixir 的 ORM,但是得益於 elixir 語言,和傳統的 ORM 相比,更加簡潔和強大。 ecto 主要分為 4 部分: 1. Repo: 這是和真正資料庫交互的部分 2. Schema: 相當於是資料庫中表的定義,但不僅僅是定義 3. Changeset ...
  • 題目如下: 這題我剛開始被示例給迷惑了,是將key和value分開輸入的,類似於cin>>key>>value,這裡應該是要講每行字元串連接成一個新的字元串,然後遍歷整個字元串,遇到:表示key錄入完畢,遇到,和},要先判斷,的情況,確定,前面沒有},這是才表示value錄入完畢。再就是首碼的問題, ...
  • 1.請求異常處理 請求異常類型: 請求超時處理(timeout): 實現代碼: import requestsfrom requests import exceptions #引入exceptions A:請求超時 def timeout_request(): try: response = req ...
  • hasattr(x, y) getattr(x, y) setattr(x, y , v) delattr(x, y)四種反射方法,就是把字元串反射為記憶體地址。 ...
  • 搭建環境 1、win10_X64,其他Win版本也可以。 2、PyCharm版本:Professional-2016.2.3。 搭建準備 1、到PyCharm官網下載PyCharm安裝包。 2、選擇Windows系統的專業版下載。 安裝軟體 1、雙擊安裝包進行安裝。 2、自定義軟體安裝路徑(建議路徑 ...
  • 正所謂怕什麼來什麼,這是知名的“墨菲定律”。Java基礎涵蓋各個方面,敢說Java基礎扎實的人不是剛畢業的學生,就是工作N年的程式員。工作N年的程式員甚至也不敢人人都說Java基礎扎實,甚至精通,往往只是“無他唯熟爾”——熟手而已。 IO這塊我確實怕,它不難,只有兩個方面:輸入/輸出。但你說它用得多 ...
  • 作為PyCharm編輯器的起步,我們理所當然的先寫一個Hello word,並運行它。(此文獻給對IDE不熟悉的初學者) 1,新建一個項目 File --> New Project... 2,新建一個文件 右鍵單擊剛建好的helloWord項目,選擇New --> Python File 3,輸入文 ...
  • 相同點: 它們都可以用於指定執行該腳本使用Python解釋器。 不同點: ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...