鑑於對不少初學編程的小夥伴來講,對於賦值和深淺拷貝的用法有些疑問,因此我就結合python變量存儲的特性從內存的角度來談一談賦值和深淺拷貝~~~python
一些基本的定義:編程
幾個術語的解釋函數
深淺拷貝的做用spa
對於不可變對象的深淺拷貝3d
不可變對象類型(這個不可變對象類型裏面不能包含可變對象類型,如元祖裏面包含列表就不知足這個條件),沒有被拷貝的說法,即使是用深拷貝,查看id的話也是同樣的,若是對其從新賦值,也只是新建立一個對象,替換掉舊的而已。一句話就是,不可變類型,不論是深拷貝仍是淺拷貝,地址值和拷貝後的值都是同樣的。指針
舉個栗子:code
n1 = 123123 n2 = n1 print(n1,n2) print(id(n1)) print(id(n2)) 輸出結果: 123123 123123 1607915318992 1607915318992
在以上代碼塊當中,a2與a1所賦的值是同樣的,都是數字123123。由於python有一個重用機制,對於同一個數字,python並不會開闢一塊新的內存空間,而是維護同一塊內存地址,只是將該數字對應的內存地址的引用賦值給變量a1和a2。因此根據輸出結果,a1和a2其實對應的是同一塊內存地址,只是兩個不一樣的引用罷了。一樣的,對於a2 = a1,其實效果等同於「a1 = 123123; a2 = 123123」,它也就是將a1指向123123的引用賦值給a2。字符串跟數字的原理雷同,若是把123123改爲「abcabc」也是同樣的。對象
結論:對於經過用 = 號賦值,數字和字符串在內存當中用的都是同一塊地址。blog
一樣的栗子:內存
import copy # 使用淺拷貝須要導入copy模塊 n1 = 123123 n3 = copy.copy(n1) # 使用copy模塊裏的copy()函數就是淺拷貝了 print(n1,n3) print(id(n1)) print(id(n3)) 輸出結果: 123123 123123 2735567515344 2735567515344
經過使用copy模塊裏的copy()函數來進行淺拷貝,把a1拷貝一份賦值給a3,查看輸出結果發現,a1和a3的內存地址仍是同樣。
結論:對於淺拷貝,數字和字符串在內存當中用的也是同一塊地址。
再來一個栗子:
import copy n1 = 123123 n4 = copy.deepcopy(n1) # 深拷貝是用copy模塊裏的deepcopy()函數 print(n1,n4) print(id(n1)) print(id(n4)) 輸出結果: 123123 123123 2545114525392 2545114525392
這個。。。還用說嘛,直接看結論>>>
結論:綜上所述,對於數字和字符串的賦值、淺拷貝、深拷貝在內存當中用的都是同一塊地址。
原理圖:
再舉個栗子
n1 = {"k1": "wu", "k2": 123, "k3": ["alex", 678]} n2 = n1 # 賦值 print(n1,n2) print(id(n1)) print(id(n2)) n1['k1'] = 'c' n1['k3'][0] = 'd' print(n1,n2) print(id(n1)) print(id(n2)) 輸出結果:
{'k1': 'wu', 'k2': 123, 'k3': ['alex', 678]} {'k1': 'wu', 'k2': 123, 'k3': ['alex', 678]}
1867471875528
1867471875528
{'k1': 'c', 'k2': 123, 'k3': ['d', 678]} {'k1': 'c', 'k2': 123, 'k3': ['d', 678]}
1867471875528
1867471875528
咱們的栗子當中用了一個字典n1,字典裏面嵌套了一個列表,當咱們把n1賦值給n2時,內存地址並無發生變化,由於其實它也是隻是把n1的引用拿過來賦值給n2而已(咱們用了一個字典來舉例,其餘類型也是同樣的)。正由於如此,當咱們修改字典裏面的數據時,n1和n2都會發生改變。
結論:對於賦值,字典、列表等其餘類型用的內存地址不會變化。
栗子走起
import copy n1 = {"k1": "wu", "k2": 123, "k3": ["alex", 678]} n3 = copy.copy(n1) # 淺拷貝 print(n1,n3) print("第一層字典的內存地址:") print(id(n1)) print(id(n3)) print("第二層嵌套的列表的內存地址:") print(id(n1["k3"])) print(id(n3["k3"])) n1['k1'] = 'tom' n1['k3'][0] = 'jack' print('***************') print(n1,n3) 輸出結果: {'k1': 'wu', 'k2': 123, 'k3': ['alex', 678]} {'k1': 'wu', 'k2': 123, 'k3': ['alex', 678]} 第一層字典的內存地址: 1506727325128 1506727325200 第二層嵌套的列表的內存地址: 1506758960840 1506758960840 *************** {'k1': 'tom', 'k2': 123, 'k3': ['jack', 678]} {'k1': 'wu', 'k2': 123, 'k3': ['jack', 678]}
經過以上結果能夠看出,進行淺拷貝時,咱們的字典第一層n1和n3指向的內存地址已經改變了,可是對於第二層裏的列表並無拷貝,它的內存地址仍是同樣的。原理以下圖:
結論:因此對於淺拷貝,字典、列表等類型,它們只拷貝第一層地址。
栗子:
import copy n1 = {"k1": "wu", "k2": 123, "k3": ["alex", 678]} n4 = copy.deepcopy(n1) # 深拷貝 print("第一層字典的內存地址:") print(id(n1)) print(id(n4)) print("第二層嵌套的列表的內存地址:") print(id(n1["k3"])) print(id(n4["k3"])) n1['k1'] = 'tom' n1['k3'][0] = 'jack' print(n1,n4)
輸出結果:
{'k1': 'wu', 'k2': 123, 'k3': ['alex', 678]} {'k1': 'wu', 'k2': 123, 'k3': ['alex', 678]}
第一層字典的內存地址:
1853270748616
1853271588800
第二層嵌套的列表的內存地址:
1853273351880
1853273350600
***************
{'k1': 'tom', 'k2': 123, 'k3': ['jack', 678]} {'k1': 'wu', 'k2': 123, 'k3': ['alex', 678]}
經過以上結果發現,進行深拷貝時,字典裏面的第一層和裏面嵌套的地址都已經變了。對於深拷貝,它會拷貝多層,將第二層的列表也拷貝一份,若是還有第三層嵌套,那麼第三層的也會拷貝,可是對於裏面的最小元素,好比數字和字符串,這裏就是「wu」,123,「alex」,678之類的,按照python的機制,它們會共同指向同一個位置,它的內存地址是不會變的。原理以下圖:
結論:對於深拷貝,字典、列表等類型,它裏面嵌套多少層,就會拷貝多少層出來,可是最底層的數字和字符串地址不變,是同樣的。
PS:
對於元祖來講,若是他裏面的元素都是不可變類型的元素,那麼不管是賦值,淺拷貝仍是深拷貝,他們的id都是同樣的(不只整個元祖的id是同樣的,裏面每個元素的id都是同樣的)。可是若是元祖裏面的元素有可變元素,如列表字典等,那麼對於賦值和淺拷貝來講,id仍然仍是同樣的(不只整個元祖的id是同樣的,裏面每個元素的id都是同樣的);對於深拷貝來講,元祖的id和列表字典的id是不同的,可是對於最底層的數字,字符串地址仍是同樣的。