Python中的賦值與深淺拷貝

  鑑於對不少初學編程的小夥伴來講,對於賦值和深淺拷貝的用法有些疑問,因此我就結合python變量存儲的特性從內存的角度來談一談賦值和深淺拷貝~~~python

準備知識

一些基本的定義:編程

  • 在Python中對象的賦值其實就是對象的引用。當建立一個對象,把它賦值給另外一個變量的時候,python並無拷貝這個對象,只是拷貝了這個對象的引用而已。
  • 淺拷貝:拷貝了最外圍的對象自己,內部的元素都只是拷貝了一個引用而已。也就是,把對象複製一遍,可是該對象中引用的其餘對象我不復制
  • 深拷貝:外圍和內部元素都進行了拷貝對象自己,而不是引用。也就是,把對象複製一遍,而且該對象中引用的其餘對象我也複製。

幾個術語的解釋函數

  • 變量:是一個系統表的元素,擁有指向對象的鏈接空間
  • 對象:被分配的一塊內存,存儲其所表明的值
  • 引用:是自動造成的從變量到對象的指針
  • 注意:類型(int類型,long類型(python3已去除long類型,只剩下int類型的數據))屬於對象,不是變量
  • 不可變對象:一旦建立就不可修改的對象,包括字符串、元組、數字
  • 可變對象:能夠修改的對象,包括列表、字典。

深淺拷貝的做用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是不同的,可是對於最底層的數字,字符串地址仍是同樣的。

相關文章
相關標籤/搜索