一文帶你掌握Python中的深淺拷貝
第一章:一切從“賦值”引發(fā)的問題開始
在 Python 的世界里,變量不僅僅是存儲數(shù)據(jù)的標(biāo)簽,更像是指向內(nèi)存中某個(gè)對象的“指針”。很多初學(xué)者,甚至是有一定經(jīng)驗(yàn)的開發(fā)者,都曾在深淺拷貝的問題上栽過跟頭。
想象一個(gè)場景:你有一個(gè)列表,里面包含了一些子列表,你想復(fù)制一份用來做修改,保留原數(shù)據(jù)。于是你順手寫了這樣一行代碼:
original_list = [[1, 2], [3, 4]]
new_list = original_list
# 現(xiàn)在,我想修改 new_list 的第一個(gè)子列表
new_list[0][0] = 999
print("Original:", original_list)
print("New:", new_list)
如果你的預(yù)期是 Original 保持 [[1, 2], [3, 4]],而 New 變?yōu)?[[999, 2], [3, 4]],那么很遺憾,現(xiàn)實(shí)會給你沉重一擊。運(yùn)行結(jié)果是:
Original: [[999, 2], [3, 4]]
New: [[999, 2], [3, 4]]
為什么?
這就是 Python 中最基礎(chǔ)但也最容易被忽視的概念:賦值(Assignment)并不是拷貝。
在上面的代碼中,new_list = original_list 并沒有創(chuàng)建一個(gè)新的列表對象,它僅僅是創(chuàng)建了一個(gè)新的引用(reference)。這就好比你有兩個(gè)名字(original_list 和 new_list),但它們都指向同一個(gè)實(shí)體(內(nèi)存中的列表對象)。因此,通過任何一個(gè)名字去修改這個(gè)實(shí)體,另一個(gè)名字看到的自然也是修改后的樣子。
為了徹底解決這個(gè)問題,我們需要深入理解 Python 內(nèi)存模型中的三個(gè)層次:賦值、淺拷貝和深拷貝。
第二章:淺拷貝(Shallow Copy)——“只復(fù)制第一層”
當(dāng)我們意識到直接賦值不是復(fù)制時(shí),我們通常會轉(zhuǎn)向淺拷貝。在 Python 中,實(shí)現(xiàn)淺拷貝的方法有很多:
- 切片操作:
new_list = old_list[:] - 工廠函數(shù):
new_list = list(old_list) copy模塊:import copy; new_list = copy.copy(old_list)
讓我們看看淺拷貝的表現(xiàn):
import copy
original_list = [[1, 2], [3, 4]]
shallow_copied_list = copy.copy(original_list)
# 修改外層
shallow_copied_list.append([5, 6])
# 修改內(nèi)層(嵌套對象)
shallow_copied_list[0][0] = 888
print("Original:", original_list)
print("Shallow:", shallow_copied_list)
輸出結(jié)果:
Original: [[888, 2], [3, 4]]
Shallow: [[888, 2], [3, 4], [5, 6]]
分析:
- 外層修改:
shallow_copied_list增加了一個(gè)元素[5, 6],original_list沒變。這說明最外層的容器確實(shí)是新創(chuàng)建的。 - 內(nèi)層修改:
shallow_copied_list[0][0]被改為 888,original_list也跟著變了。這說明內(nèi)層的子列表并沒有被復(fù)制。
什么是淺拷貝?
淺拷貝(Shallow Copy)會創(chuàng)建一個(gè)新的容器對象,但不會遞歸地復(fù)制容器內(nèi)的元素。新容器中填充的是原容器中元素的引用。
對于不可變對象(如整數(shù)、字符串、元組),引用就引用吧,反正改不了。但如果你的列表里包含了可變對象(如列表、字典、集合),那么這些可變對象的引用被共享了,這就是所謂的“共享子對象”(Shared Sub-objects)。
適用場景:淺拷貝適用于你的數(shù)據(jù)結(jié)構(gòu)是“扁平”的,或者你明確知道你需要共享子對象(這很少見)。
第三章:深拷貝(Deep Copy)——“斬?cái)嗨辛b絆”
如果你需要一個(gè)完全獨(dú)立的副本,無論嵌套多少層,修改副本都不影響原件,那么你需要的是深拷貝。
深拷貝使用 copy.deepcopy() 實(shí)現(xiàn):
import copy
original_list = [[1, 2], [3, 4]]
deep_copied_list = copy.deepcopy(original_list)
# 徹底修改副本
deep_copied_list[0][0] = 777
deep_copied_list.append([9, 0])
print("Original:", original_list)
print("Deep:", deep_copied_list)
輸出結(jié)果:
Original: [[1, 2], [3, 4]]
Deep: [[777, 2], [3, 4], [9, 0]]
什么是深拷貝?
深拷貝會遞歸地遍歷原對象的所有子對象,并創(chuàng)建它們的副本。這意味著新對象和原對象在內(nèi)存中是完全獨(dú)立的,沒有任何引用重疊。
深拷貝的陷阱與高級用法:
雖然深拷貝很強(qiáng)大,但它也有代價(jià)(性能開銷大)和陷阱。
遞歸引用導(dǎo)致死循環(huán):
如果一個(gè)對象直接或間接引用了自己,deepcopy 會拋出 RecursionError。
a = [1] a.append(a) # a 現(xiàn)在是 [1, [...]] # b = copy.deepcopy(a) # 這會報(bào)錯(cuò)
自定義類的拷貝控制:如果你需要控制類的深拷貝行為,可以實(shí)現(xiàn) __deepcopy__ 方法。這在處理數(shù)據(jù)庫連接、文件句柄等不可序列化或不可拷貝的資源時(shí)非常有用。
性能考量:對于巨大的數(shù)據(jù)結(jié)構(gòu),深拷貝可能非常慢。如果你的嵌套層級很淺,或者全是不可變數(shù)據(jù),深拷貝就是殺雞用牛刀。
第四章:核心原理圖解與常見誤區(qū)
為了徹底理清關(guān)系,我們可以通過一張簡化的內(nèi)存示意圖來理解:
假設(shè) a = [1, [2, 3]]
賦值 (b = a):
a-> [ptr1, ptr2]b-> [ptr1, ptr2] (指向同一個(gè)地址)
淺拷貝 (b = copy.copy(a)):
a-> [ptr1, ptr2]b-> [ptr3, ptr4]- 其中
ptr1 == ptr3(指向同一個(gè)整數(shù) 1,整數(shù)不可變所以無所謂) - 但是
ptr2 == ptr4(指向同一個(gè)列表[2, 3]) -> 這是問題的根源
深拷貝 (b = copy.deepcopy(a)):
a-> [ptr1, ptr2]b-> [ptr5, ptr6]ptr5指向新的整數(shù) 1ptr6指向一個(gè)新的列表[2, 3],且該新列表內(nèi)的元素也是新的。
常見誤區(qū):字典的copy()方法
很多 Python 開發(fā)者會直接用字典自帶的 .copy() 方法,認(rèn)為這就是深拷貝。
錯(cuò)誤! 字典的 .copy() 也是淺拷貝!
d1 = {'a': [1, 2]}
d2 = d1.copy()
d2['a'].append(3)
print(d1) # 輸出 {'a': [1, 2, 3]},d1 被修改了!
正確的做法依然是 copy.deepcopy(d1) 或者使用 d1.copy() 配合字典推導(dǎo)式(如果只有一層的話)。
第五章:總結(jié)與最佳實(shí)踐
搞懂了深淺拷貝,我們其實(shí)是在搞懂 Python 的對象引用模型。這是編寫健壯、無副作用代碼的基石。
最后的建議:
- 默認(rèn)使用引用:除非你明確需要副本,否則不要隨意拷貝。
- 優(yōu)先考慮淺拷貝:如果數(shù)據(jù)結(jié)構(gòu)是扁平的,或者你確定不需要處理嵌套可變對象,
list[:]或copy.copy()更快。 - 不確定時(shí)用深拷貝:對于復(fù)雜的嵌套結(jié)構(gòu),為了數(shù)據(jù)的安全性,
copy.deepcopy()是最穩(wěn)妥的選擇。 - 警惕函數(shù)副作用:在函數(shù)中修改傳入的可變參數(shù)時(shí),一定要想清楚這是不是你想要的行為。如果不想修改原數(shù)據(jù),記得在函數(shù)內(nèi)部先拷貝。
以上就是一文帶你掌握Python中的深淺拷貝的詳細(xì)內(nèi)容,更多關(guān)于Python深淺拷貝的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
一文帶你玩轉(zhuǎn)Python必備的幾種數(shù)據(jù)格式
在Python開發(fā)中,數(shù)據(jù)格式的選擇直接影響著程序的性能和可維護(hù)性,本文將詳細(xì)介紹Python開發(fā)中最常用的幾種數(shù)據(jù)格式,希望可以幫助大家選擇最合適的數(shù)據(jù)表示方式2025-06-06
Python裝飾器使用示例及實(shí)際應(yīng)用例子
這篇文章主要介紹了Python裝飾器使用示例及實(shí)際應(yīng)用例子,本文給出了斐波拉契數(shù)列、注冊回調(diào)函數(shù)、mysql封裝、線程異步等實(shí)際使用示例,需要的朋友可以參考下2015-03-03
python?字符串常用方法超詳細(xì)梳理總結(jié)
字符串是Python中基本的數(shù)據(jù)類型,幾乎在每個(gè)Python程序中都會使用到它。本文為大家總結(jié)了Python中必備的31個(gè)字符串方法,需要的可以參考一下2022-03-03

