I. 簡介
深拷貝會遞歸的創建一個完全獨立的對象副本,包括所有嵌套的對象,而淺拷貝只復制嵌套對象的引用,不復制嵌套對象本身。
簡單來說就是兩者都對原對象進行了復制,因此使用is運算符來比較新舊對象時,返回的都是False(都開辟了新的內存);兩者區別在于對嵌套對象有沒有進行遞歸的復制。淺拷貝沒有給嵌套對象復制并分配新內存,用is來比較嵌套對象時返回的是True;而深拷貝對嵌套對象開辟了進行了復制并分配新內存,用is來比較嵌套對象時返回的是False。
一個例子如下,我們分別對鏈表的頭結點執行深拷貝與淺拷貝:
# 原鏈表 ↓
a1 -> b1 -> c1 -> d1 -> e1
# 淺拷貝 ↓ 對于嵌套對象b1, c1, ..., 直接采用了原有引用
a2 -> b1 -> c1 -> d1 -> e1
# 深拷貝 ↓ 對于嵌套對象,同樣開辟了內存空間將其復制
a2 -> b2 -> c2 -> d2 -> e2
從代碼實現來講,深拷貝可以用copy庫的deepcopy方法實現;淺拷貝除了用copy庫的copy方法,還有許多其他的實現途徑,接下來我們將進行介紹。
II. 列表
A. 首先要注意一點,對于常用的等號賦值操作,這一操作并沒有進行任何拷貝,只是創建了對現有對象的一個新引用:
arr1 = [1, 2, 3, 4]
arr2 = arr1
print(arr2 is arr1) # True
arr2[0] = 0
print(arr1) # [0, 2, 3, 4]
B. 對列表進行切片屬于淺拷貝操作:
arr1 = [1, 2, 3, 4]
arr2 = arr1[:]
print(arr2 is arr1) # False
arr2[0] = 0
print(arr1) # [1, 2, 3, 4]
C. 淺拷貝并不會復制嵌套對象:
arr1 = [1, 2, 3, [4, 5, 6]]
arr2 = arr1[:]
print(arr2 is arr1) # False(最外層被復制)
print(arr2[-1] is arr1[-1]) # True(嵌套對象沒有被復制)
arr2[-1][0] = 0
print(arr1) # [1, 2, 3, [0, 5, 6]](被修改)
D. 深拷貝才會復制嵌套對象:
import copy
arr1 = [1, 2, 3, [4, 5, 6]]
arr2 = copy.deepcopy(arr1)
print(arr2 is arr1) # False
print(arr2[-1] is arr1[-1]) # False(嵌套對象也被復制)
arr2[-1][0] = 0
print(arr1) # [1, 2, 3, [4, 5, 6]](未修改)
E. 使用數據類型本身的構造器仍屬于淺拷貝:
arr1 = [1, 2, 3, [4, 5, 6]]
arr2 = list(arr1) # 使用構造器創建新對象, 屬于淺拷貝
print(arr2 is arr1) # False
print(arr2[-1] is arr1[-1]) # True
arr2[-1][0] = 0
print(arr1) # [1, 2, 3, [0, 5, 6]]
F. 對列表進行修改所返回的新列表也屬于淺拷貝(先淺拷貝再修改):
arr1 = [1, 2, 3, [4, 5, 6]]
arr2 = arr1 + [] # 先淺拷貝再修改
print(arr2 is arr1) # False
print(arr2[-1] is arr1[-1]) # True
arr2[-1][0] = 0
print(arr1) # [1, 2, 3, [0, 5, 6]]
III. 字符串
A. Python中的字符串是不可變對象。因此,如果對其進行完整切片[:],可以發現這一過程并沒有對字符串本身進行修改。那么Python此時只會直接記錄原字符串對象的引用,不進行任何拷貝。從設計動機的角度理解,既然本身不可修改,并且進行的切片操作也沒有進行修改,那么復制的意義不大,所以干脆不進行復制:
s1 = "1234"
s2 = s1[:]
print(s2 is s1) # True(引用的內容相同)
B. 以上結論同樣適用于對字符串進行"假修改",此時也不會進行任何拷貝:
s1 = "1234"
s2 = s1 + ""
print(s2 is s1) # True(沒有進行實質修改)
C. 想要進行拷貝,那就得對字符串進行實質修改。如果切片運算改變了原字符串的內容,由于字符串是不可變的,因此只能開辟一個新的內存,來存儲修改后的字符串。此時進行了拷貝過程。注意,由于字符串本身沒法嵌套對象,因此這里不區分深拷貝與淺拷貝:
s1 = "1234"
s2 = s1[::-1][::-1] # 進行兩次修改,翻轉兩次
print(s2 is s1) # False
print(s2) # 1234
s3 = s1 + "5"
print(s3 is s1) # False
D. 使用構造方法str,也不會進行任何拷貝,只是創建了另一個指向原字符串對象的引用:
s1 = "1234"
s2 = str(s1)
print(s2 is s1) # True
E. 使用copy或deepcopy都不能對字符串內容進行拷貝,只會新增一個引用:
import copy
s1 = "1234"
s2 = copy.copy(s1)
s3 = copy.deepcopy(s1)
print(s2 is s1) # True
print(s3 is s1) # True