圖解 Python 淺拷貝與深拷貝
Python 中的賦值語句不會建立物件的拷貝,僅僅只是將名稱繫結至一個物件。對於不可變物件,通常沒什麼差別,但是處理可變物件或可變物件的集合時,你可能需要建立這些物件的 “真實拷貝”,也就是在修改建立的拷貝時不改變原始的物件。
本文將以圖文方式介紹 Python 中複製或“克隆”物件的操作。
首先介紹一下 Python 中淺拷貝與深拷貝的區別:
- 淺拷貝:淺拷貝意味著構造一個新的集合物件,然後用原始物件中找到的子物件的引用來填充它。從本質上講,淺層的複製只有一層的深度。複製過程不會遞迴,因此不會建立子物件本身的副本。
- 深拷貝:深拷貝使複製過程遞迴。這意味著首先構造一個新的集合物件,然後遞迴地用在原始物件中找到的子物件的副本填充它。以這種方式複製一個物件,遍歷整個物件樹,以建立原始物件及其所有子物件的完全獨立的克隆。
賦值與引用
在開始淺拷貝與深拷貝前,我們先來看一下 Python 中的賦值與引用。
lst = [1, 2, 3]
new_list = lst
從字面上看,上述語句建立了變數 lst 和 new_list,並且 lst 和 new_list 的賦值都為一個列表。但是,Python 的賦值語句並不會複製物件,而是會重新建立一個物件的引用。
可以看出,lst 和 new_list 都引用了同一個列表。
建立淺拷貝
不少教程裡都會提到,如果你有一個列表,當你想要修改列表中的值但卻不想影響原始物件時,可以使用 list 複製(淺拷貝)一個列表。
我們先來試一下:
lst = [1, 2, 3]
new_list = list(lst)
沒錯,lst 和 new_list 分別指向了不同的列表。當修改 lst 列表中的值時,並不會對 new_list 物件產生影響。
lst[0] = 'x'
print(lst)
print(new_list)
['x', 2, 3]
[1, 2, 3]
之所以說 list 語句是淺拷貝,是因為這種修改只對一層物件有效,當列表中有子物件時,對子物件的修改將影響原始物件和淺拷貝物件。
為了解釋這一說法,讓我們先建立一個巢狀列表,並使用 list 函式建立淺拷貝。
lst = [[1, 2, 3], [4, 5, 6]]
new_list = list(lst)
這裡 new_list 是有著和 lst 一樣內容的新的獨立的物件。
可以看到 lst 和 new_list 分別指向了不同的物件。
對第一層 lst 的修改,將不會對 new_list 副本造成影響。
lst.append([7, 8, 9])
print(lst)
print(new_list)
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
[[1, 2, 3], [4, 5, 6]]
但是,因為我們只建立了原始列表的一個淺拷貝,所以 new_list 仍然包含對 lst 中儲存的原始子物件的引用。
也就是如上圖所示,lst 和 new_list 的子列表都指向了相同的物件。
子物件沒有被複制,它們只是在複製的列表中被再次引用。
因此,當你修改 lst 中的一個子物件時,這種修改也會反映到 new_list 中—— 這是因為兩個列表共享相同的子物件。這種複製只是一個淺的,一個層級的複製:
lst[0][0] = 'x'
print(lst)
print(new_list)
[['x', 2, 3], [4, 5, 6], [7, 8, 9]]
[['x', 2, 3], [4, 5, 6]]
如果我們在第一步中建立了一個 lst 的深拷貝,那麼兩個物件就完全獨立了。這是物件的淺拷貝和深拷貝之間的實際區別。
使用 Python 標準庫中的 copy 模組可以建立深拷貝,這個模組為建立任意 Python 物件的淺拷貝和深拷貝提供了一個簡單的介面。
建立深拷貝
這次我們使用 deepcopy() 函式建立一個物件的深拷貝:
import copy
lst = [[1, 2, 3], [4, 5, 6]]
new_list = copy.deepcopy(lst)
從圖中可以看出 lst 和 new_list 中的子物件指向了不同的物件,如果對 lst 的子物件進行修改,將不會影響 new_list。
這一次,原始物件和複製物件都是完全獨立的。如前面所說,遞迴克隆了 lst,包括它的所有子物件:
lst[0][0] = 'x'
print(lst)
print(new_list)
[['x', 2, 3], [4, 5, 6]]
[[1, 2, 3], [4, 5, 6]]
copy 模組中的 copy.copy() 函式也可以建立物件的淺拷貝。使用 copy.copy() 可以明確地表示建立淺拷貝。對於內建集合,簡單地使用 list、dict 和 set 等工廠函式來建立淺拷貝是更加 Pythonic 的。
複製任意 Python 物件
copy.copy() 和 copy.deepcopy() 函式可用於複製任意物件。以前面的列表複製示例為基礎。讓我們從定義一個簡單的 2D 點類開始:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f'Point({self.x!r}, {self.y!r})'
__repr__() 函式使我們可以輕鬆地在 Python 直譯器中檢查從這個類建立的物件。
接下來,我們將建立一個 Point 例項,然後使用 copy 模組複製(淺拷貝)它:
a = Point(23, 42)
b = copy.copy(a)
print(a is b)
False
a 和 b 分別指向了不同的 Point 例項。因為我們的 Point 物件使用不可變型別(int)作為其座標,所以在這種情況下,淺拷貝和深拷貝沒有區別。但我馬上會展開這個例子。
接下來定義另一個類來表示 2D 矩形。矩形將使用 Point 物件來表示它們的座標:
class Rectangle:
def __init__(self, topleft, bottomright):
self.topleft = topleft
self.bottomright = bottomright
def _repr__(self):
return (f'Rectangle({self.topleft!r}, {self.bottomright!r})')
# 建立一個 Rectangle 例項的淺拷貝
rect = Rectangle(Point(0, 1), Point(5, 6))
shallow_rect = copy.copy(rect)
print(rect)
print(shallow_rect)
print(rect is shallow_rect)
Rectangle(Point(0, 1), Point(5, 6))
Rectangle(Point(0, 1), Point(5, 6))
False
跟前面 list 的例子一樣,rect 和 shallow_rect 的子物件都有相同的引用。在物件層級中修改一個物件,將看到這個變化也反映在淺拷貝的副本中:
rect.topleft.x = 999
print(rect)
print(shallow_rect)
Rectangle(Point(999, 1), Point(5, 6))
Rectangle(Point(999, 1), Point(5, 6))
接下來建立 Rectangle 的深拷貝並對其進行修改:
deep_rect = copy.deepcopy(rect)
deep_rect.topleft.x = 222
print(rect)
print(shallow_rect)
print(deep_rect)
Rectangle(Point(999, 1), Point(5, 6))
Rectangle(Point(999, 1), Point(5, 6))
Rectangle(Point(222, 1), Point(5, 6))
可以看出,深拷貝完全獨立於原始物件和淺拷貝物件。
參閱 copy 模組文件 可以對複製進行進一步的研究。例如,物件可以通過定義特殊的方法 __copy__() 和 __deepcopy__() 來控制如何複製它們。
謹記三件事
- 建立物件的淺拷貝不會克隆子物件。因此,拷貝不會完全獨立於原始物件。
- 一個物件的深拷貝會遞迴地克隆子物件。克隆物件完全獨立於原始物件,但是建立深拷貝速度較慢。
- 可以使用 copy 模組複製任意物件(包括自定義類)。
https://www.toutiao.com/a6729394838715761164/
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69946223/viewspace-2655035/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Python淺拷貝與深拷貝Python
- python深拷貝與淺拷貝Python
- 淺談深拷貝與淺拷貝?深拷貝幾種方法。
- JS深拷貝與淺拷貝JS
- 深入淺出深拷貝與淺拷貝
- python 指標拷貝,淺拷貝和深拷貝Python指標
- 深拷貝、淺拷貝與Cloneable介面
- 賦值、淺拷貝與深拷貝賦值
- React之淺拷貝與深拷貝React
- 物件的深拷貝與淺拷貝物件
- 【c++】淺拷貝與深拷貝C++
- vue深拷貝淺拷貝Vue
- 深入淺出的“深拷貝與淺拷貝”
- JavaScript中的淺拷貝與深拷貝JavaScript
- 淺拷貝與深拷貝的實現
- 【JavaScript】物件的淺拷貝與深拷貝JavaScript物件
- 淺拷貝與深拷貝程式碼(javascript)JavaScript
- Python3之淺談----深拷貝與淺拷貝Python
- jquery之物件拷貝深拷貝淺拷貝案例講解jQuery物件
- iOS深拷貝和淺拷貝iOS
- Java深拷貝和淺拷貝Java
- 物件深拷貝和淺拷貝物件
- javascript 淺拷貝VS深拷貝JavaScript
- JavaScript深拷貝和淺拷貝JavaScript
- js 淺拷貝和深拷貝JS
- js 深拷貝和淺拷貝JS
- JavaScript淺拷貝和深拷貝JavaScript
- js深拷貝和淺拷貝JS
- js 深拷貝 vs 淺拷貝JS
- 【JS】深拷貝與淺拷貝,實現深拷貝的幾種方法JS
- 理解JS中的淺拷貝與深拷貝JS
- Java 輕鬆理解深拷貝與淺拷貝Java
- 深拷貝與淺拷貝的實現(一)
- 淺探js深拷貝和淺拷貝JS
- java深克隆(深拷貝)和淺克隆(淺拷貝)Java
- C++淺拷貝和深拷貝C++
- 實現物件淺拷貝、深拷貝物件
- go slice深拷貝和淺拷貝Go