陳玲
(瀘州職業技術學院,四川瀘州 646000)
初學Python的時候,可能會對Python中的可變類型和不可變類型感到疑惑,而我們實際工作中也常常會遇到Python中的可變類型和不可變類型,正確的使用它們才能產生我們預期的效果。下面本文將從概念、判斷、常見的可變與不可變數據類型、函數參數傳遞的區別等方面對Python的可變類型和不可變類型進行分析。
從字面意思來理解,可變類型對象就是指對象的內容可變,而不可變類型對象就是指對象的內容不可變。我們來看一個例子:

示例源代碼 示例運行結果a = 10 print(id(a))a = 20 print(id(a))b = [10]print(id(b))b[0] = 20 print(id(b))140727772846016 140727772846336 2778581568704 2778581568704
在上面的例子中,id函數用于獲取對象的內存地址。把a的值從10修改成20時,a對象的內存地址發生了變化,說明執行a=20時并不是將a所指向的內存地址中存儲的10直接修改成20,而是在內存中新申請了一段內存空間來存儲對象20,然后a再指向對象20,所以兩次id(a)的值的不同,如圖1所示。而b[0]=20,修改列表的第一個元素,兩次id(b)的值相同,說明b指向的內存空間不變,是在存儲10的原有內存空間基礎上直接將10修改成20,如圖2所示。

圖1 修改不可變類型

圖2 修改可變類型
當修改該數據類型對應變量的值,如果存儲變量值對應的內存地址發生了改變,這種數據類型就是不可變數據類型。如果存儲變量值對應的內存地址沒有發生改變,這種數據類型就是可變數據類型。如上例中的整型,將a從10修改成20,存儲20的內存地址已經是一個新地址了,a的引用地址從140727772846016變成140727772846336,已經發生了變化,所以整型是不可變數據類型。將列表b的第一個元素修改成20,b的引用地址依舊是1309544287232,沒有發生改變,所以列表是可變數據類型。
從結果來看,不可變類型變量和可變類型變量的值都能修改,所謂不可變類型,是指不能直接修改變量所引用的內存地址中的這個值,當要改變變量的賦值時,其實是在內存中重新開辟了一段內存空間,將修改后的數據存儲在這個新的內存地址里,變量不再引用原數據的內存地址而是引用新的內存地址了。所謂可變類型,是指變量所引用的內存地址處的值是可以改變的。
如何判斷Python中的數據類型是可變類型還是不可變類型呢?根據上述的解釋,只需要判斷變量所引用的內存地址處的值能否直接修改,即修改后判斷變量所引用的內存地址值是否改變,通過id()來獲取變量引用的內存地址,value值改變,id值不變即為可變類型,value值改變,id值也隨之改變即為不可變類型。即只需要在改變value值時,使用id()查看變量所引用的內存地址是否變化,就可以知道這種數據類型是可變的還是不可變的。例如:

示例源代碼 示例運行結果flag = T rue print(id(flag))flag = False print(id(flag))140727770666832 140727770666864
從結果來看,當改變布爾類型變量的值時,變量id值也改變了,所以布爾類型是不可變類型。舉一反三,我們能得出判斷出Python中常用的可變類型和不可變類型,如下:
整數、浮點數、布爾類型、字符串、元組。
列表、字典、集合。
在調用Python函數時,根據函數的定義情況,有時需要傳遞實參。當函數修改了形參的值時,函數調用結束時,實參類型的不同會導致實參最終的結果不同。我們來看一個傳遞不可變類型實參的例子:

傳遞不可變類型a = 10 print("調用前,a = {},引用地址為{}。".form at(a,id(a)))def m odifyInt(b):# 定義函數print("b 的引用地址為{}。".form at(id(b)))b = 20 print("修改后b 的引用地址為{}。".form at(id(b)))m odifyInt(a)# 調用函數print("調用后,a = {},引用地址為{}。".form at(a,id(a)))運行結果如下:調用前,a = 10,引用地址為140727770945472。b 的引用地址為140727770945472。修改后b 的引用地址為140727770945792。調用后,a = 10,引用地址為140727770945472。

圖3 傳遞不可變類型
函數modifyInt()修改形參b的值為20,調用函數時傳遞了實參a,是整數,為不可變類型,函數調用結束后,a的值仍為10,函數雖然修改了形參的值,但并沒有修改實參的值。從運行結果來看,形參b的引用地址和實參a的引用地址相同,說明函數調用時實際上是把a的引用傳遞給了b,因為b為不可變類型,所以b=20時,是在內存重新開辟了一段內存空間存儲20,然后b的引用指向了這個新的內存地址。如圖3所示,當傳遞不可變類型的實參時,傳遞的是引用,函數修改形參的值并不會修改實參的值[1]。
接下來看一個傳遞可變類型實參的例子:

傳遞可變類型a = [10]print("調用前,a = {},引用地址為{}。".form at(a,id(a)))def m odifyList(b):# 定義函數print("b 的引用地址為{}。".form at(id(b)))b[0] = 20 print("修改后b 的引用地址為{}。".form at(id(b)))m odifyList(a)# 調用函數print("調用后,a = {}".form at(a))運行結果如下:調用前,a = [10],引用地址為1782725414080。b 的引用地址為1782725414080。修改后b 的引用地址為1782725414080。調用后,a = [20]
函數modifyList()修改形參b的第一個元素為20,調用函數時傳遞了實參a,是列表,為可變類型,函數調用結束后,a的第一個元素被修改成了20。函數修改了形參的值,導致了實參值的改變。從運行結果來看,形參b的引用地址和實參a的引用地址相同,說明函數調用時仍然是把a的引用傳遞給了b,因為b為可變類型,所以在b引用指向的內存地址中直接修改了這個值,而沒有重新開辟內存空間來保存這個修改后的值。如圖4所示。當傳遞可變類型的實參時,傳遞的是引用,函數修改形參的值后,實參的值也發生改變[2]。

圖4 傳遞可變類型
對于不可變類型,變量(引用)指向的地址的內容是不可變的,改變變量的值只是將變量(引用)指向了新的地址。對于可變類型,變量(引用)指向的地址的內容是可變的,改變變量的值就是直接對原地址內容的改變。整數、浮點數、字符串、布爾類型、元組都是不可變類型,列表、字典、集合是可變類型,在進行函數參數傳遞時,雖然都是引用傳遞,但要區分傳遞的是可變類型還是不可變類型。傳遞不可變類型時,改變形參的值,并不會改變實參的值;傳遞可變類型時,改變形參的值,會改變實參的值。