5張圖徹底理解Python中的淺拷貝與深拷貝
假設你去面試 Python 開發崗,面試官如果對基礎比較看重的話,那麼很可能會問你這樣的問題
“談談你對 Python 中的淺拷貝和深拷貝的理解?”
若平時你在開發中像我一樣,過度使用 deepcopy,以至於忘記了淺拷貝(shallow copy)和深拷貝(deep copy)的區別,那很可能要栽大跟頭了。建議在讀這篇文章之前,看下我之前寫的文章 《你真的理解Python中的賦值、傳參嗎?》 ,它有助於你更快的理解本文
Python 的引用計數
首先我們要知道,Python 內不可變物件的記憶體管理方式是引用計數。因此,我們在談論拷貝時,其實談論的主要特點都是基於可變物件的。我們來看下面這段程式碼
import copy a = "張小雞" b = a c = copy.copy(a) d = copy.deepcopy(a) print "賦值:id(b)->>>", id(b) print "淺拷貝:id(c)->>>", id(c) print "深拷貝:id(d)->>>", id(c) 複製程式碼
輸出如下
賦值:id(b)->>> 4394180400 淺拷貝:id(c)->>> 4394180400 深拷貝:id(d)->>> 4394180400 複製程式碼

因為我們這裡操作的是不可變物件,Python 用引用計數的方式管理它們,所以 Python 不會對值相同的不可變物件,申請單獨的記憶體空間。只會記錄它的引用次數
淺拷貝
我們先來比較一下淺拷貝和賦值在可變物件上的區別
import copy a = ["張小雞"] b = a c = copy.copy(a) print "賦值:id(b)->>>", id(b) print "淺拷貝:id(c)->>>", id(c) 複製程式碼
輸出結果
賦值:id(b)->>> 4473562824 淺拷貝:id(c)->>> 4462057592 複製程式碼

發現沒有,賦值就是對物體進行貼標籤操作,作用於同一物體。而淺拷貝則會建立一個新的物件,至於物件中的元素,它依然會引用原來的物體,我們再來看一段例子
import copy a = ["張小雞"] print "改變前,a內部的元素id:id([a])->>>", [id(_) for _ in a] c = copy.copy(a) print "改變前,淺拷貝c內部的元素id:id([c])->>>", [id(_) for _ in c] a[0] = "姬無命" print "改變後,a內部的元素id:id([a])->>>", [id(_) for _ in a] print "改變後,淺拷貝c內部的元素id:id([c])->>>", [id(_) for _ in c] 複製程式碼
輸出如下
改變前,a內部的元素id:id([a])->>> [4318150256] 改變前,淺拷貝c內部的元素id:id([c])->>> [4318150256] 改變後,a內部的元素id:id([a])->>> [4318150352] 改變後,淺拷貝c內部的元素id:id([c])->>> [4318150256] 複製程式碼

操作不可變物件時,由於引用計數的特性,被拷貝的元素改變時,就相當於撕掉了原來的標籤,重新貼上新的標籤一樣,對於我們已拷貝的元素沒有任何影響。因此在操作不可變物件時,淺拷貝和深拷貝是沒有區別的
import copy import json a = [["張小雞"], "姬無命"] print "改變前,a的值", json.dumps(a, ensure_ascii=False) print "改變前,a內部的元素id:id([a])->>>", [id(_) for _ in a] c = copy.copy(a) print "改變前,c的值", json.dumps(c, ensure_ascii=False) print "改變前,淺拷貝c內部的元素id:id([c])->>>", [id(_) for _ in c] a[0][0] = "Tom" a[1] = "Jack" print "改變後,a的值", json.dumps(a, ensure_ascii=False) print "改變後,c的值", json.dumps(c, ensure_ascii=False) print "改變後,a內部的元素id:id([a])->>>", [id(_) for _ in a] print "改變後,淺拷貝c內部的元素id:id([c])->>>", [id(_) for _ in c] 複製程式碼
輸出結果
改變前,a的值 [["張小雞"], "姬無命"] 改變前,a內部的元素id:id([a])->>> [4385503208, 4373939232] 改變前,c的值 [["張小雞"], "姬無命"] 改變前,淺拷貝c內部的元素id:id([c])->>> [4385503208, 4373939232] 改變後,a的值 [["Tom"], "Jack"] 改變後,c的值 [["Tom"], "姬無命"] 改變後,a內部的元素id:id([a])->>> [4385503208, 4373938320] 改變後,淺拷貝c內部的元素id:id([c])->>> [4385503208, 4373939232] 複製程式碼

由於淺拷貝會使用原始元素的引用(記憶體地址)。所以在在操作被拷貝物件內部的可變元素時,其結果是會影響到拷貝物件的
深拷貝
深拷貝遇到可變物件,則又會進行一層物件建立,所以你操作被拷貝物件內部的可變物件,不影響拷貝物件內部的值
import copy import json a = [["張小雞"], "姬無命"] print "改變前,a的值", json.dumps(a, ensure_ascii=False) print "改變前,a內部的元素id:id([a])->>>", [id(_) for _ in a] d = copy.deepcopy(a) print "改變前,d的值", json.dumps(d, ensure_ascii=False) print "改變前,深拷貝d內部的元素id:id([d])->>>", [id(_) for _ in d] a[0][0] = "Tom" a[1] = "Jack" print "改變後,a的值", json.dumps(a, ensure_ascii=False) print "改變後,d的值", json.dumps(d, ensure_ascii=False) print "改變後,a內部的元素id:id([a])->>>", [id(_) for _ in a] print "改變後,深拷貝d內部的元素id:id([d])->>>", [id(_) for _ in d] 複製程式碼
輸出如下
改變前,a的值 [["張小雞"], "姬無命"] 改變前,a內部的元素id:id([a])->>> [4337440744, 4325876768] 改變前,d的值 [["張小雞"], "姬無命"] 改變前,深拷貝d內部的元素id:id([d])->>> [4337440888, 4325876768] 改變後,a的值 [["Tom"], "Jack"] 改變後,d的值 [["張小雞"], "姬無命"] 改變後,a內部的元素id:id([a])->>> [4337440744, 4325875856] 改變後,深拷貝d內部的元素id:id([d])->>> [4337440888, 4325876768] 複製程式碼
