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
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...