前言
- Python 中不存在值傳遞,一切傳遞的都是物件的引用,也可以認為是傳址
- 這裡會講三個概念:物件賦值、淺拷貝、深拷貝
名詞解釋
- 變數:儲存物件的引用
- 物件:會被分配一塊記憶體,儲存實際的資料,比如字串、數字、列表
- 引用:變數指向物件,可以理解為指標
實際的一種應用場景
- 有一個變數 a,儲存了一個值
- 此時想用另一個變數 b 暫時儲存變數 a 的值,以便後續使用
- 然後繼續修改變數 a 的值,但修改的時候並不想同步更改變數 b 的值
a=1 b=a a=2
物件賦值
- 賦值運算子詳解:https://www.cnblogs.com/poloyy/p/15083012.html
- Python 的賦值語句並不是建立一個新物件,只是建立了一個共享原始物件引用的新變數
不可變物件的賦值
a = 1 b = a print(a, b) a += 2 print(a, b) print("a id:", id(a)) print("b id:", id(b)) # 輸出結果 1 1 2 1 a id: 4564097808 b id: 4564097776
- 修改變數 a 的值,不會同步修改變數 b 的值
- 因為賦值操作 a += 2 後,變數 a 儲存的物件引用已經改變了
- 至於具體的原理,可以看看不可變物件、可變物件的詳解 https://www.cnblogs.com/poloyy/p/15073168.html
可變物件的賦值
a = [1, 2, 3] b = a print(a, b) a[1] = 22 print(a, b) print("a id:", id(a)) print("b id:", id(b)) # 輸出結果 [1, 2, 3] [1, 2, 3] [1, 22, 3] [1, 22, 3] a id: 4567683136 b id: 4567683136
- 修改 a 變數的值,會同步修改變數 b 的值,這不符合上面的說的實際應用場景
- 因為變數 a、b 指向的物件是可變物件,所以它們儲存的物件引用都是一樣的
拷貝的誕生
- 那如果要讓可變物件也能滿足上述實際應用場景,要怎麼做呢?
- 當然就是拷貝
- 而拷貝又分為淺拷貝、深拷貝,接下來會具體聊一聊兩種拷貝的區別
第一個重點總結
- 對於不可變物件來說,賦值操作其實就可以滿足上面說的實際應用場景
- 所以!後面要講的淺拷貝、深拷貝對於不可變物件來說,和賦值操作是一樣的效果!
- 記住!淺拷貝、深拷貝只針對可變物件,即列表、集合、字典!
copy 模組
Python 提供了 copy 模組,包含了淺拷貝、深拷貝函式
from copy import copy, deepcopy # 淺拷貝 copy(x) # 深拷貝 deepcopy(x)
淺拷貝
一句話概括:淺拷貝會建立一個新物件,該新物件儲存原始元素的引用
淺拷貝後的值是相同的
- 將列表賦值給變數 old_list
- 通過 copy() 方法對 old_list 變數指向的物件進行淺拷貝,並賦值給新變數 new_list
- 因為是物件進行拷貝,所以 new_list 和 old_list 儲存的值是相同的
import copy old_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] new_list = copy.copy(old_list) print("Old list:", old_list) print("New list:", new_list) # 輸出結果 Old list: [[1, 2, 3], [4, 5, 6], [7, 8, 9]] New list: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
淺拷貝後的會產生一個新的物件
- 雖然 old_list 和 new_list 儲存的值是相同的,但淺拷貝的操作是產生了一個新的物件
- 所以 old_list 和 new_list 指向的物件並不是同一個
import copy old_list = [[1, 2], [3, 4]] new_list = copy.copy(old_list) old_list.append([5, 6]) print("Old list:", old_list, "id is :", id(old_list)) print("New list:", new_list, "id is :", id(new_list)) # 輸出結果 Old list: [[1, 2], [3, 4], [5, 6]] id is : 4366240704 New list: [[1, 2], [3, 4]] id is : 4366246720
可以看到記憶體地址是不同的,所以給 old_list 新增一個元素並不會同步讓 new_list 也新增
原理圖
- 淺拷貝生成了一個新物件,然後賦值給 new_list
- new_list、old_list 指向的列表物件不是同一個,但值相同
- 重點:對於列表物件中的元素,淺拷貝產生的新物件只儲存原始元素的引用(記憶體地址),所以兩個列表物件的元素的引用都指向同一個記憶體地址
那為什麼要深拷貝呢?
修改列表內的不可變物件元素
上面的栗子是直接新增元素,來看看修改元素會怎麼樣
# 不可變元素 import copy old_list = [1, 2, "string", (1, 2,)] new_list = copy.copy(old_list) old_list[1] += 22 old_list[2] += "s" old_list[3] += (3,) print("Old list:", old_list) print("New list:", new_list) # 輸出結果 Old list: [1, 24, 'strings', (1, 2, 3)] New list: [1, 2, 'string', (1, 2)]
修改 old_list 的三種不可變物件元素,均不會同步給 new_list
修改不可變物件的原理圖
修改列表內的可變物件元素
# 可變元素 import copy old_list = [[1, 2], [3, 4]] new_list = copy.copy(old_list) old_list[0][0] += 99 old_list[1][0] += 97 print("Old list:", old_list, "old list id:", id(old_list), " old list[0] id:", id(old_list[0])) print("new list:", new_list, "new list id:", id(new_list), " new list[0] id:", id(new_list[0])) # 輸出結果 Old list: [[100, 2], [100, 4]] old list id: 4430308096 old list[0] id: 4430302400 new list: [[100, 2], [100, 4]] new list id: 4430308416 new list[0] id: 4430302400
從輸出結果看到
- 兩個變數儲存了不同的物件引用
- 但是可變物件元素的記憶體地址仍然是同一個
修改可變物件的原理圖
總結
- 修改可變物件是在原始物件上直接操作的
- 淺拷貝產生的新物件是儲存的仍然是原始物件的記憶體地址
- 所以修改可變物件的時候,新物件的值也會被同步修改,因為新舊列表物件的元素的引用是指向同一個記憶體地址
- 當修改可變物件的時候,不滿足一開始說的實際應用場景,所以誕生了深拷貝
深拷貝
- 建立一個新物件,且儲存的物件引用也是新的
- 深,意味著會把所有子元素物件也複製生成一個新物件
栗子一
# 深拷貝 old_list = [[1, 2], [3, 4]] new_list = copy.deepcopy(old_list) old_list[0][0] += 99 old_list[1][0] += 97 print("Old list:", old_list, "old list id:", id(old_list), " old list[0] id:", id(old_list[0])) print("new list:", new_list, "new list id:", id(new_list), " new list[0] id:", id(new_list[0])) # 輸出結果 Old list: [[100, 2], [100, 4]] old list id: 4430308480 old list[0] id: 4430211392 new list: [[1, 2], [3, 4]] new list id: 4430308096 new list[0] id: 4430308864
從輸出結果看到
- 兩個變數儲存了不同的物件引用
- 可變物件元素(子物件)的記憶體地址也是不同的
栗子二
假設是一個三維列表呢
# 深拷貝-三維陣列 old_list = [[1, [10, 9]], [3, 4]] new_list = copy.deepcopy(old_list) old_list[0][1][0] += 90 print("Old list:", old_list) print("New list:", new_list) # 輸出結果 Old list: [[1, [100, 9]], [3, 4]] New list: [[1, [10, 9]], [3, 4]]
兩個變數依舊是獨立的
深拷貝原理圖