Python 中變數賦值傳遞時的引用和複製介紹

大雄45發表於2021-04-10
導讀 Python 賦值過程中不明確區分複製和引用,一般對靜態變數的傳遞為複製,對動態變數的傳遞為引用。

Python 中變數賦值傳遞時的引用和複製介紹Python 中變數賦值傳遞時的引用和複製介紹

曾經看到這樣一個問題,一個字典中的元素是列表,將這個列表元素賦值給一個變數,然後修改這個列表中元素的值,結果發現,字典中那個列表也同樣修改了。那個問題如下:

dict = {'a':[1,2,3,4,5],'b':2}
x = dict['a']
for i in range(5):
    x[i] = 0
print(dict['a'])

程式執行結果如下:

[0, 0, 0, 0, 0]

這兒涉及到Python賦值到底是引用還是複製一份的問題,即賦值時是傳值還是傳址。上面問題是將"a"的值賦給了x出現了上述情況,如果是將"b"的值賦給了x,當我們修改x的值時,字典dict的值並不受影響。

>>> dict = {'a':[1,2,3,4,5],'b':2}
>>> x = dict['b']
>>> x
2
>>> x=x+3
>>> x
5
>>> dict
{'a': [1, 2, 3, 4, 5], 'b': 2}
>>>

那麼問題來了,變數賦值傳遞時什麼情況下是傳值(複製),什麼情況下是傳址(引用)呢?

直接複製

當我們不知道是引用還是複製的情況下,可以顯式的複製。比如字典物件本身都具有複製的方法:

x=dict.copy()

沒有複製方法的物件,也是可以複製的。這兒我們引入一個深複製的概念,深複製——即python的copy模組提供的一個deepcopy方法。深複製會完全複製原變數相關的所有資料,在記憶體中生成一套完全一樣的內容,在這個過程中我們對這兩個變數中的一個進行任意修改都不會影響其他變數。還是上面的程式碼,如果改成如下:

import copy
dict = {'a':[1,2,3,4,5],'b':2}
x = copy.deepcopy(dict['a'])
for i in range(5):
    x[i] = 0
print(dict['a'])

執行結果dict值不受影響。

除了深複製,copy模組還提供一個copy方法,稱其為淺複製,對於簡單的物件,深淺複製都是一樣的,上面的詞典物件的copy方法就是淺複製。

>>> dict
{'a': [8, 2, 3, 4, 5], 'b': 4}
>>> dd=copy.copy(dict)
>>> dd
{'a': [8, 2, 3, 4, 5], 'b': 4}
>>> dd['a'][0]=7
>>> dd
{'a': [7, 2, 3, 4, 5], 'b': 4}
>>> dict
{'a': [7, 2, 3, 4, 5], 'b': 4}
>>> ee=dict.copy()
>>> ee
{'a': [7, 2, 3, 4, 5], 'b': 4}
>>> ee['a'][0]=9
>>> ee
{'a': [9, 2, 3, 4, 5], 'b': 4}
>>> dict
{'a': [9, 2, 3, 4, 5], 'b': 4}
>>> ee['b']=5
>>> ee
{'a': [9, 2, 3, 4, 5], 'b': 5}
>>> dict
{'a': [9, 2, 3, 4, 5], 'b': 4}
>>>

淺複製時改變第一層次相互不受影響(上例中詞典b值的修改),第二層次(上例中詞典a的列表值修改)就相互影響了,改一個,其他跟著變。看看id吧:

>>> id(dict)
20109472
>>> id(dd)
20244496
>>> id(ee)
20495072
>>> id(dd['a'])
20272112
>>> id(ee['a'])
20272112
>>> id(dict['a'])
20272112
>>>

可見詞典各個複製的id是不同的,但詞典a值的id是相同的。如果我們需要真正意義的複製,就用深複製吧。

傳遞規則

Python 賦值過程中不明確區分複製和引用,一般對靜態變數的傳遞為複製,對動態變數的傳遞為引用。(注,對靜態變數首次傳遞時也是引用,當需要修改靜態變數時,因為靜態變數不能改變,所以需要生成一個新的空間儲存資料)。

  1. 字串,數值,元組均為靜態變數
  2. 列表,字典為動態變數

變數有時比較複雜,存在組合現象,比如字典中包含列表,列表中包含字典,但賦值時,總是屬於某個型別。如果實在不清楚狀況,可以試驗一下,用id()這個函式看看,如果是引用,兩個變數指向的地址是相同的。例如:

>>> a=6
>>> id(a)
10413476
>>> b=a
>>> id(b)
10413476
>>> b=8
>>> id(b)
10413452
>>>

修改變數b之前,a和b指向的地址是相同的,修改b後,地址就變了。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69955379/viewspace-2767443/,如需轉載,請註明出處,否則將追究法律責任。

相關文章