Python學習之共享引用

ARM的程式設計師敲著詩歌的夢發表於2019-03-09

Python 學習之共享引用

什麼是共享引用

假設我們在Python互動模式下輸入以下語句:

>>> a = 3
>>> b = a

在這裡插入圖片描述

實際的效果就是變數a和b都引用了相同的物件(指向了相同的記憶體空間)。這在Python中叫做共享引用——多個變數名引用了同一個物件。

如果再來一條語句

>>> a = 'spam'

會怎麼樣?

變數a引用了由常量表示式‘spam’所建立的新物件,但是變數b仍然引用原始的物件3,因為這個賦值運算改變的不是物件3,它僅僅改變了變數a的指向,變數b並沒有發生改變。

在這裡插入圖片描述

在Python中,變數總是一個指向物件的指標,而不是可改變的記憶體區域的標籤(比如C語言中的變數)。給一個變數賦一個新值,並不是修改了原始的物件,而是讓這個變數去引用完全不同的一個物件。

注意:當可變的物件以及原處的改變進入這個場景,上述情形會有某種改變。

共享引用和在原處修改(Shared References and In-Place Changes)

有一些物件和操作確實會在原處改變物件。例如,在一個列表中通過偏移進行賦值,這確實會改變這個列表物件,而不是生成一個新的列表物件。對於支援這種在原處修改的物件,共享引用的時候一定要小心,因為對一個變數的修改會影響其他變數。

請看下面的語句:

>>> L1 = [2, 3, 4]
>>> L2 = L1

L1 是一個包含了物件2、3、4的列表。列表中的元素是通過他們的位置進行讀取的,所以L1[0]引用物件2. 當然,列表自身也是物件,就像整數和字串一樣。在執行了上面兩行語句後,L1和L2引用了相同的物件。

現在加上第3行:

>>> L1 = [2, 3, 4]
>>> L2 = L1
>>> L1[0] = 24

我們看一下L1和L2的值:

>>> L1
[24, 3 ,4]
>>> L2
[24, 3 ,4]

在這裡,沒有改變L1,改變了L1所引用的物件的一個元素,這類修改會覆蓋列表物件中的某部分。因為這個列表物件同時被L1和L2引用,所以在原處修改不僅僅會影響L1,也會影響L2。雖然我們沒有改變L2,但是它的值將發生變化。

如果你不想要這樣的結果,那麼需要拷貝物件,而不是建立引用。有很多拷貝列表的辦法,最簡單的辦法是從頭到尾的切片。

>>> L1 = [2, 3, 4]
>>> L2 = L1[:]
>>> L1[0] = 24
>>> L1
[24, 3, 4]
>>> L2
[2, 3, 4]

這裡,對L1的修改不會影響L2,因為L2引用的是L1所引用物件的一個拷貝。也就是說,L1和L2指向了不同的記憶體區域。

共享引用和相等

>>> x = 42
>>> x = 'shrubbery'    

因為Python快取並複用了小的整數和小的字串,執行完這兩行程式碼後,物件42也許不會被回收;相反地,它可能仍被儲存在一個系統表中,等待下一次你的程式碼生成另一個42來重複利用。儘管這樣,大多數種類的物件都會在不被引用的時候馬上回收。

在Python中有2種不同的方法去檢查兩個變數是否相等。

>>> L = [1, 2, 3]
>>> M = L                 # M and L reference the same object
>>> L == M                # Same values
True
>>> L is M                # Same objects
True

==操作符測試兩個被引用的物件是否有相同的值,這種方法往往在Python中用作相等的檢查。

is操作符用來檢查物件的同一性。如果兩個變數名都指向同一個物件,則會返回 True,所以這是一種更嚴格的相等測試。

實際上,is只是比較實現引用的指標,所以這是一種檢測共享引用的方法。如果變數名指向不同的物件,就算這兩個物件的值相等,也會返回 False.

例如:

>>> L = [1, 2, 3]
>>> M = [1, 2, 3]         # M and L reference different objects
>>> L == M                # Same values
True
>>> L is M                # Different objects
False

如果對小的數字進行類似測試:

>>> X = 42
>>> Y = 42                # Should be two different objects
>>> X == Y
True
>>> X is Y                # Same object anyhow: caching at work!
True

按理來說,第3~4行是可以理解的,因為兩個物件的值一樣;但是第5~6行就讓人匪夷所思了,X引用的42和Y引用的42本來是2個物件,應該輸出False才對,不過,因為小的整數和字串被快取並複用了,所以is告訴我們X和Y引用了同一個物件。

實際上,你可以用sys模組中的getrefcount函式查詢物件的被引用次數。例如,我們查一下整數1被引用的次數:

>>> import sys
>>> sys.getrefcount(1)   
812

這種物件快取和複用的機制與程式碼是沒有關係的。Python這樣做是為了提高執行速度。

【End】




參考資料

《Python學習手冊(第4版)》,機械工業出版社

相關文章