假設你去面試 Python 開發崗,面試官若是對基礎比較看重的話,那麼極可能會問你這樣的問題html
「談談你對 Python 中的淺拷貝和深拷貝的理解?」python
若平時你在開發中像我同樣,過分使用 deepcopy,以致於忘記了淺拷貝(shallow copy)和深拷貝(deep copy)的區別,那極可能要栽大跟頭了。建議在讀這篇文章以前,看下我以前寫的文章《你真的理解Python中的賦值、傳參嗎?》,它有助於你更快的理解本文git
首先咱們要知道,Python 內不可變對象的內存管理方式是引用計數。所以,咱們在談論拷貝時,其實談論的主要特色都是基於可變對象的。咱們來看下面這段代碼github
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 不會對值相同的不可變對象,申請單獨的內存空間。只會記錄它的引用次數json
咱們先來比較一下淺拷貝和賦值在可變對象上的區別bash
import copy
a = ["張小雞"]
b = a
c = copy.copy(a)
print "賦值:id(b)->>>", id(b)
print "淺拷貝:id(c)->>>", id(c)
複製代碼
輸出結果spa
賦值:id(b)->>> 4473562824
淺拷貝:id(c)->>> 4462057592
複製代碼
發現沒有,賦值就是對物體進行貼標籤操做,做用於同一物體。而淺拷貝則會建立一個新的對象,至於對象中的元素,它依然會引用原來的物體,咱們再來看一段例子3d
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]
複製代碼
輸出以下code
改變前,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]
複製代碼
所以,在下次咱們遇到這類問題時,咱們說出如下關鍵點,基本就很穩了