1. 程式人生 > >Python的可變與不可變資料型別

Python的可變與不可變資料型別

python的可變與不可變資料型別

    初學python的時候,可能會有很多疑惑,尤其是最先接觸的所謂的“可變資料型別”和“不可變資料型別”。python與C/C++不一樣,它的變數使用有自己的特點,當初學python的時候,一定要記住“一切皆為物件,一切皆為物件的引用”這句話,其實這個特點類似於JAVA,所以在python裡面大家也不用擔心類似於C/C++中的指標的複雜問題。下面本文將對python裡面的“可變資料型別”和“不可變資料型別”進行分析。     首先,我們需要知道在python中哪些是可變資料型別,哪些是不可變資料型別。可變資料型別:列表list和字典dict;不可變資料型別:整型int、浮點型float、字串型string和元組tuple。
    然後,我們以int和list為例,來看看“可變資料型別”和“不可變資料型別”到底有什麼區別。

(1)不可變資料型別分析。先來看一段程式:

>>> x = 1
>>> id(x)
31106520
>>> y = 1
>>> id(y)
31106520
>>> x = 2
>>> id(x)
31106508
>>> y = 2
>>> id(y)
31106508
>>> z = y
>>> id(z)
31106508

>>> x += 2
>>> id(x)
31106484 

上面這段程式都是對不可變資料型別中的int型別的操作,id()檢視的是當前變數的地址值。我們先來看x = 1和y = 1兩個操作的結果,從上面的輸出可以看到x和y在此時的地址值是一樣的,也就是說x和y其實是引用了同一個物件,即1,也就是說記憶體中對於1只佔用了一個地址,而不管有多少個引用指向了它,都只有一個地址值,只是有一個引用計數會記錄指向這個地址的引用到底有幾個而已。當我們進行x = 2賦值時,發現x的地址值變了,雖然還是x這個引用,但是其地址值卻變化了,後面的y = 2以及z = y,使得x、y和z都引用了同一個物件,即2,所以地址值都是一樣的。當x和y都被賦值2後,1這個物件已經沒有引用指向它了,所以1這個物件佔用的記憶體,即31106520地址要被“垃圾回收”,即1這個物件在記憶體中已經不存在了。最後,x進行了加2的操作,所以建立了新的物件4,x引用了這個新的物件,而不再引用2這個物件。
    那麼為什麼稱之為不可變資料型別呢?這裡的不可變大家可以理解為x引用的地址處的值是不能被改變的,也就是31106520地址處的值在沒被垃圾回收之前一直都是1,不能改變,如果要把x賦值為2,那麼只能將x引用的地址從31106520變為31106508,相當於x = 2這個賦值又建立了一個物件,即2這個物件,然後x、y、z都引用了這個物件,所以int這個資料型別是不可變的,如果想對int型別的變數再次賦值,在記憶體中相當於又建立了一個新的物件,而不再是之前的物件。從下圖中就可以看到上面程式的過程。 圖1 python不可變資料型別分析

從上面的過程可以看出,不可變資料型別的優點就是記憶體中不管有多少個引用,相同的物件只佔用了一塊記憶體,但是它的缺點就是當需要對變數進行運算從而改變變數引用的物件的值時,由於是不可變的資料型別,所以必須建立新的物件,這樣就會使得一次次的改變建立了一個個新的物件,不過不再使用的記憶體會被垃圾回收器回收。

(2)可變資料型別分析。下面同樣先看一段程式。

>>> a = [1, 2, 3]
>>> id(a)
41568816
>>> a = [1, 2, 3]
>>> id(a)
41575088
>>> a.append(4)
>>> id(a)
41575088
>>> a += [2]
>>> id(a)
41575088
>>> a
[1, 2, 3, 4, 2]

從上面的程式中可以看出,進行兩次a = [1, 2, 3]操作,兩次a引用的地址值是不同的,也就是說其實建立了兩個不同的物件,這一點明顯不同於不可變資料型別,所以對於可變資料型別來說,具有同樣值的物件是不同的物件,即在記憶體中儲存了多個同樣值的物件,地址值不同。接著來看後面的操作,我們對列表進行新增操作,分別a.append(4)和a += [2],發現這兩個操作使得a引用的物件值變成了上面的最終結果,但是a引用的地址依舊是41575088,也就是說對a進行的操作不會改變a引用的地址值,只是在地址後面又擴充了新的地址,改變了地址裡面存放的值,所以可變資料型別的意思就是說對一個變數進行操作時,其值是可變的,值的變化並不會引起新建物件,即地址是不會變的,只是地址中的內容變化了或者地址得到了擴充。下圖對這一過程進行了圖示,可以很清晰地看到這一過程。


從上述過程可以看到,可變資料型別是允許同一物件的內容,即值可以變化,但是地址是不會變化的。但是需要注意一點,對可變資料型別的操作不能是直接進行新的賦值操作,比如說a = [1, 2, 3, 4, 5, 6, 7],這樣的操作就不是改變值了,而是新建了一個新的物件,這裡的可變只是對於類似於append、+=等這種操作。     總之,用一句話來概括上述過程就是:“python中的不可變資料型別,不允許變數的值發生變化,如果改變了變數的值,相當於是新建了一個物件,而對於相同的值的物件,在記憶體中則只有一個物件,內部會有一個引用計數來記錄有多少個變數引用這個物件;可變資料型別,允許變數的值發生變化,即如果對變數進行append、+=等這種操作後,只是改變了變數的值,而不會新建一個物件,變數引用的物件的地址也不會變化,不過對於相同的值的不同物件,在記憶體中則會存在不同的物件,即每個物件都有自己的地址,相當於記憶體中對於同值的物件儲存了多份,這裡不存在引用計數,是實實在在的物件。”     最後,大家主要區分開什麼是變數值的變化、什麼是變數引用的物件地址的變化這些概念就很清楚了,千說萬說都不如自己動手寫一些程式測試一下看看來得實在,所以建議大家有什麼疑惑可以自己寫一些基本的程式測試一下便知道結果。如果大家有新的意見,歡迎補充,謝謝。