'圖解 Python 淺拷貝與深拷貝'
Python 中的賦值語句不會創建對象的拷貝,僅僅只是將名稱綁定至一個對象。對於不可變對象,通常沒什麼差別,但是處理可變對象或可變對象的集合時,你可能需要創建這些對象的 “真實拷貝”,也就是在修改創建的拷貝時不改變原始的對象。
本文將以圖文方式介紹 Python 中複製或“克隆”對象的操作。
首先介紹一下 Python 中淺拷貝與深拷貝的區別:
- 淺拷貝:淺拷貝意味著構造一個新的集合對象,然後用原始對象中找到的子對象的引用來填充它。從本質上講,淺層的複製只有一層的深度。複製過程不會遞歸,因此不會創建子對象本身的副本。
- 深拷貝:深拷貝使複製過程遞歸。這意味著首先構造一個新的集合對象,然後遞歸地用在原始對象中找到的子對象的副本填充它。以這種方式複製一個對象,遍歷整個對象樹,以創建原始對象及其所有子對象的完全獨立的克隆。
賦值與引用
在開始淺拷貝與深拷貝前,我們先來看一下 Python 中的賦值與引用。
lst = [1, 2, 3]
new_list = lst
從字面上看,上述語句創建了變量 lst 和 new_list,並且 lst 和 new_list 的賦值都為一個列表。但是,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)
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]
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 一樣內容的新的獨立的對象。
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]]
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]]
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)
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]]
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
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
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))
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 模塊複製任意對象(包括自定義類)。