Python_賦值和深淺copy

Python中賦值和深淺copy存儲變化    

  在剛學python的過程中,新手會對python中賦值、深淺copy的結果感到丈二和尚。經過多次在網上查找這幾個過程的解釋說明以及實現操作觀察,初步對這幾個過程有了淺顯的認識。以下內容僅是我在學習過程中遇到的問題,然後上網搜驗證,最後理解。博文也許沒有將這部分內容寫明白,也許有不對的地方,如果有大佬看到希望能指點一下新人。隨着後面的學習以及理解會再次補充此內容。

  • id函數

  id方法的返回值就是對象的內存地址

  • 執行賦值語句

  在python中執行一條賦值語句,python解釋器做了這兩件事情:

  1.在內存中創建一個字符串;

  2.在內存中創建一個變量,並把字符串的內存地址賦值給變量;

1 >>> a='haizei'
2 >>> id(a)
3 1895098520440    #字符串‘haizei’的內存地址

  python解釋器在內存中創建一個字符串‘haizei’,創建一個指針a,並把字符串的內存地址保存在指針中。指針就指向了字符串,訪問指針a就是訪問字符串‘haizei’。

  • 賦值a賦值給變量b

1 >>> a='haizei'
2 >>> id(a)
3 1895098520440
4 >>> b=a
5 >>> id(b)
6 1895098520440    #a和b的指向的值的內存地址是相同的,說明賦值是把變量a指向的值的內存地址賦值給b,使b和a一樣也指向同一個內存地址。b和a都是存儲的值的內存地址

  執行b=a,創建了一個變量b,並把b指向a指向的字符串的內存地址。

  執行b=a,創建一個指針b,並把存儲在指針a中的內存地址賦值給指針b,這樣指針b就也指向了a指向的字符串。賦值完成後b就和a沒有任何關係了,只和它指向的字符串有關係。

  • 重新給a賦值
 1 >>> a='haizei'
 2 >>> id(a)
 3 1895098520440
 4 >>> b=a
 5 >>> id(b)
 6 1895098520440
 7 >>> a='lufei'   #python解釋器在內存中新開闢一塊空間創建字符串‘lufei’,並把a重新指向字符串‘abc’
 8 >>> id(a)
 9 1895098520496
10 >>> id(b)
11 1895098520440   #變量b指向的還是原來的字符串

   前面給變量a賦值,把a的值賦給b,結果是在內存中創建了一個值,a和b都指向了這個值。現在重新給a賦一個新的值,執行一條賦值語句,過程是在內存中創建一個新的值,然後把新值的內存地址保存在變量指針a中。結果是內存中存在兩個值b指向原來的值,a指向新創建的值。以上結果說明對於賦值操作,如果是將另一個變量的值賦給一個變量實際上是將另一個變量存儲的值的內存地址同樣保存在被賦予值的變量中;如果是給變量賦一個新的值,那麼相當於是一個初始化操作會在內存中開闢一段空間創建一個值,將值的內存地址保存在被賦值的變量指針中。

  • 創建複雜數據結構
1 >>> name=['lufei','suolong','namei']
2 >>> id(name)
3 1895098506504
4 >>> id(name[0])
5 1895098520496
6 >>> id(name[1])
7 1895098520608
8 >>> id(name[2])
9 1895098520664

  創建的列表的名稱爲name,從代碼結果可以看到:列表name指向的整體內存地址和列表中每個元素指向的地址都不相同。說明對於複雜的數據結構來說,裏面存儲的也只是一個個值的內存地址。從下圖可以看出來對於元組、列表、字典這樣的複雜的數據結構,在內存中其變量和值是分開的,每個元素都對應一個變量指針,變量指針指向值的位置。變量和值的存儲是分開的,把整個數據結構賦值給另一個變量的執行過程是在內存中創建相同數量的一些變量,然後把原數據結構中存儲在變量中的值的地址賦值給新創建的變量。結果如右圖。

  

  對列表中的元素的增刪改查操作不會影響到列表本身的內存地址,改變的是列表內的某個元素的地址引用;如果是重新初始化一個列表那麼修改的是列表名稱指向的整個列表的內存地址。可當我們給name這個列表重新初始化賦值時,就給name這個變量重新賦予了一個新的內存地址,覆蓋了原來的列表的地址。

 1 >>> name=['lufei','suolong','namei']
 2 >>> id(name)
 3 1895098506504
 4 >>> id(name[0])
 5 1895098520496
 6 >>> id(name[1])
 7 1895098520608
 8 >>> id(name[2])
 9 1895098520664
10 >>> name[0]='zhang'    #重新給序號爲0的元素賦值
11 >>> id(name)           #整個列表的內存地址沒有改變
12 1895098506504
13 >>> id(name[0])
14 1895098520552          #序號爲0的元素的內存地址發生改變
15 >>> name=['zhao','qian','sun']        #重新初始化賦值name這個變量
16 >>> id(name)
17 1895098512136                         #name這個變量指向的內存地址發生了改變

   這裏重新給name變量賦值,賦一個新的列表給name。整個過程以及結果和上面的單個變量的賦值是類似的,不同的是這裏是一個列表是多個單個元素(變量)的集合。相當於是對多個變量做這樣的操作。

  • 淺copy
 1 >>> name={'lufei':'cap','haizei':['suolong','namei','qiaoba']}   #1、創建字典name
 2 >>> a=name.copy()           #執行copy方法,吧結果賦值給字典a
 3 >>> id(name)
 4 1895098004488
 5 >>> id(a)
 6 1895098007368               #從內存地址上看原來的name的內存地址和a的內存地址不相同,說明copy是創建了一個name的副本,把副本的內存地址保存在變量a中
 7 >>> id(name['lufei'])
 8 1895098521056
 9 >>> id(a['lufei'])
10 1895098521056               #分別查看原字典name和副本字典a中鍵‘lufei’的值的內存地址,地址相同。說明這裏存儲的是同一個值
11 >>> id(name['haizei'])
12 1895098506504
13 >>> id(a['haizei'])
14 1895098506504               #同樣副本中另一個鍵的值的內存地址也是相同的,說明兩個字典內的鍵都指向相同的值
15 >>> name['lufei']='haha'    #執行賦值語句修改name字典中鍵‘lufei’的值
16 >>> name['haizei'].remove('namei')       #調用字典name中‘haizei’鍵的值,並調用值的remove方法移除列表中的‘namei’元素
17 >>> name
18 {'haizei': ['suolong', 'qiaoba'], 'lufei': 'haha'}
19 >>> a
20 {'haizei': ['suolong', 'qiaoba'], 'lufei': 'cap'}     #分別查看字典name和a,發現對字典name執行賦值語句時,副本沒有跟着修改,對鍵‘haizei’做的修改副本跟着做了修改?
21 >>> id(name)
22 1895098004488
23 >>> id(a)
24 1895098007368        #分別查看兩個字典的內存地址,和修改之前的內存地址是一樣的。說明修改複雜的數據結構中元素的值不影響變量對整體數據結構內存地址的指向(數據結構的內存地址沒有改變)
25 >>> id(name['lufei'])
26 1895098520944
27 >>> id(a['lufei'])
28 1895098521056        #分別查看兩個字典中鍵‘lufei’的值的內存地址,發現name中該鍵的值被修改了其內存地址改變了。而字典a的鍵的值沒有改變,對應的該鍵的值的內存地址沒有改變。
29 >>> id(name['haizei'])
30 1895098506504
31 >>> id(a['haizei'])
32 1895098506504        #對字典name中鍵‘haizei’的值做了修改,但是查看該鍵的值的內存地址時,兩個字典都沒有變化,和修改前是一樣的?

   在理解深淺copy的時候,先用一張圖來表示字典在內存中的存在方式,再說明深淺copy分別copy的是字典的那些部分。當對原字典或者副本進行修改時,修改的部分在 內存中造成什麼變化,是否對原字典和副本字典產生了影響。當理解了這些的時候也就理解了深淺copy

  這是我們在內存中創建的字典的存在形式。可以這麼理解,字典name保存整體字典的內存地址從而指向這個字典,字典內包含兩個鍵,鍵1保存的是一個值的內存地址從而指向這個值,鍵2保存的是一個列表的整體地址從而指向這個列表。列表內又有三個指針分別保存三個值的內存地址作爲列表的三個元素。

  淺copy複製的是哪部分?淺copy僅複製了圖中的字典變量的部分。也就是複製了鍵1下保存的內存地址和鍵而的內存地址

  當修原字典或者副本字典的鍵1關聯的值時,在內存中開闢一片新的空間存儲一個新的值,並把新創建的值的內存地址保存到被修改的字典的對應鍵的指針裏。因此被修改的字典的鍵指向了新創建的值,未被修改的字典中相同的鍵指向的還是原來的值,所以當修改的鍵對應的值是一個簡單的數據類型的值時,修改後原字典和副本字典的值出現了不同。

  當修改的鍵對應的值是一個數據結構時,由於原字典中鍵2中僅保存了列表的整體地址,副本字典中copy的時候,鍵2也僅僅是copy的整個列表的內存地址。原字典和副本字典都不清楚列表中值的內存地址是多少,只是通過列表的整體地址可以訪問到列表中的值。在這種情況下列表中的值發生變化時就對原字典和副本字典都產生了影響。

  • 深copy
 1 >>> from copy import deepcopy         #使用深copy時需要從copy模塊中導入deepcopy
 2 >>> name={'captain':'lufei','sailor':['suolong','namei','qiaoba']}
 3 >>> copy_name=deepcopy(name)          #執行深copy
 4 >>> id(name)
 5 1595431826440
 6 >>> id(copy_name)
 7 1595435745864                         #查看原字典和副本字典的內存地址,地址不同
 8 >>> id(name['sailor'])
 9 1595437385096
10 >>> id(copy_name['sailor'])
11 1595437383816                         #查看原字典和副本字典列表值對應的鍵下存儲的內存地址,地址不同。說明當鍵對應的值是一個複雜的數據結構時deepcopy會繼續複製到下一層。在淺copy中原字典和副本字典的這
12 >>> id(name['sailor'][1])             #裏是相同的。
13 1595435816248
14 >>> id(copy_name['sailor'][1])
15 1595435816248                         #當查看原字典和副本字典中列表值下的元素的地址時發現,其內存地址又是相同的。

 

  由上面的內存地址的查看可以繪製出深copy時copy的範圍對應的圖

  可以對比淺copy時的結果的圖中不一樣的地方來理解。

  

 

 

 

 

 

 

 

 

 

 

 

高級語言中,變量是對內存及其地址的抽象。在python中變量的內存地址和變量值的內存地址是分開的--python中內存的變量存儲情況。

每個變量中都存儲了這個變量的地址,而不是值本身;對於複雜的數據結構來說,裏面存儲的也只是每個元素的內存地址而已,並不是值本身;

在python中執行一條賦值語句如a='abc'python解釋器做了兩件事情:

1.在內存中創建了一個字符串‘abc’

2.在內存中創建了一個名爲a的變量,並把它指向‘abc’

變量的每次初始化,都開闢了一個新的空間,並把新內容的內存地址賦值給變量

  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章