陳玲
(瀘州職業(yè)技術(shù)學(xué)院,四川瀘州 646000)
初學(xué)Python的時(shí)候,可能會對Python中的可變類型和不可變類型感到疑惑,而我們實(shí)際工作中也常常會遇到Python中的可變類型和不可變類型,正確的使用它們才能產(chǎn)生我們預(yù)期的效果。下面本文將從概念、判斷、常見的可變與不可變數(shù)據(jù)類型、函數(shù)參數(shù)傳遞的區(qū)別等方面對Python的可變類型和不可變類型進(jìn)行分析。
從字面意思來理解,可變類型對象就是指對象的內(nèi)容可變,而不可變類型對象就是指對象的內(nèi)容不可變。我們來看一個(gè)例子:
示例源代碼 示例運(yùn)行結(jié)果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函數(shù)用于獲取對象的內(nèi)存地址。把a(bǔ)的值從10修改成20時(shí),a對象的內(nèi)存地址發(fā)生了變化,說明執(zhí)行a=20時(shí)并不是將a所指向的內(nèi)存地址中存儲的10直接修改成20,而是在內(nèi)存中新申請了一段內(nèi)存空間來存儲對象20,然后a再指向?qū)ο?0,所以兩次id(a)的值的不同,如圖1所示。而b[0]=20,修改列表的第一個(gè)元素,兩次id(b)的值相同,說明b指向的內(nèi)存空間不變,是在存儲10的原有內(nèi)存空間基礎(chǔ)上直接將10修改成20,如圖2所示。
圖1 修改不可變類型
圖2 修改可變類型
當(dāng)修改該數(shù)據(jù)類型對應(yīng)變量的值,如果存儲變量值對應(yīng)的內(nèi)存地址發(fā)生了改變,這種數(shù)據(jù)類型就是不可變數(shù)據(jù)類型。如果存儲變量值對應(yīng)的內(nèi)存地址沒有發(fā)生改變,這種數(shù)據(jù)類型就是可變數(shù)據(jù)類型。如上例中的整型,將a從10修改成20,存儲20的內(nèi)存地址已經(jīng)是一個(gè)新地址了,a的引用地址從140727772846016變成140727772846336,已經(jīng)發(fā)生了變化,所以整型是不可變數(shù)據(jù)類型。將列表b的第一個(gè)元素修改成20,b的引用地址依舊是1309544287232,沒有發(fā)生改變,所以列表是可變數(shù)據(jù)類型。
從結(jié)果來看,不可變類型變量和可變類型變量的值都能修改,所謂不可變類型,是指不能直接修改變量所引用的內(nèi)存地址中的這個(gè)值,當(dāng)要改變變量的賦值時(shí),其實(shí)是在內(nèi)存中重新開辟了一段內(nèi)存空間,將修改后的數(shù)據(jù)存儲在這個(gè)新的內(nèi)存地址里,變量不再引用原數(shù)據(jù)的內(nèi)存地址而是引用新的內(nèi)存地址了。所謂可變類型,是指變量所引用的內(nèi)存地址處的值是可以改變的。
如何判斷Python中的數(shù)據(jù)類型是可變類型還是不可變類型呢?根據(jù)上述的解釋,只需要判斷變量所引用的內(nèi)存地址處的值能否直接修改,即修改后判斷變量所引用的內(nèi)存地址值是否改變,通過id()來獲取變量引用的內(nèi)存地址,value值改變,id值不變即為可變類型,value值改變,id值也隨之改變即為不可變類型。即只需要在改變value值時(shí),使用id()查看變量所引用的內(nèi)存地址是否變化,就可以知道這種數(shù)據(jù)類型是可變的還是不可變的。例如:
示例源代碼 示例運(yùn)行結(jié)果flag = T rue print(id(flag))flag = False print(id(flag))140727770666832 140727770666864
從結(jié)果來看,當(dāng)改變布爾類型變量的值時(shí),變量id值也改變了,所以布爾類型是不可變類型。舉一反三,我們能得出判斷出Python中常用的可變類型和不可變類型,如下:
整數(shù)、浮點(diǎn)數(shù)、布爾類型、字符串、元組。
列表、字典、集合。
在調(diào)用Python函數(shù)時(shí),根據(jù)函數(shù)的定義情況,有時(shí)需要傳遞實(shí)參。當(dāng)函數(shù)修改了形參的值時(shí),函數(shù)調(diào)用結(jié)束時(shí),實(shí)參類型的不同會導(dǎo)致實(shí)參最終的結(jié)果不同。我們來看一個(gè)傳遞不可變類型實(shí)參的例子:
傳遞不可變類型a = 10 print("調(diào)用前,a = {},引用地址為{}。".form at(a,id(a)))def m odifyInt(b):# 定義函數(shù)print("b 的引用地址為{}。".form at(id(b)))b = 20 print("修改后b 的引用地址為{}。".form at(id(b)))m odifyInt(a)# 調(diào)用函數(shù)print("調(diào)用后,a = {},引用地址為{}。".form at(a,id(a)))運(yùn)行結(jié)果如下:調(diào)用前,a = 10,引用地址為140727770945472。b 的引用地址為140727770945472。修改后b 的引用地址為140727770945792。調(diào)用后,a = 10,引用地址為140727770945472。
圖3 傳遞不可變類型
函數(shù)modifyInt()修改形參b的值為20,調(diào)用函數(shù)時(shí)傳遞了實(shí)參a,是整數(shù),為不可變類型,函數(shù)調(diào)用結(jié)束后,a的值仍為10,函數(shù)雖然修改了形參的值,但并沒有修改實(shí)參的值。從運(yùn)行結(jié)果來看,形參b的引用地址和實(shí)參a的引用地址相同,說明函數(shù)調(diào)用時(shí)實(shí)際上是把a(bǔ)的引用傳遞給了b,因?yàn)閎為不可變類型,所以b=20時(shí),是在內(nèi)存重新開辟了一段內(nèi)存空間存儲20,然后b的引用指向了這個(gè)新的內(nèi)存地址。如圖3所示,當(dāng)傳遞不可變類型的實(shí)參時(shí),傳遞的是引用,函數(shù)修改形參的值并不會修改實(shí)參的值[1]。
接下來看一個(gè)傳遞可變類型實(shí)參的例子:
傳遞可變類型a = [10]print("調(diào)用前,a = {},引用地址為{}。".form at(a,id(a)))def m odifyList(b):# 定義函數(shù)print("b 的引用地址為{}。".form at(id(b)))b[0] = 20 print("修改后b 的引用地址為{}。".form at(id(b)))m odifyList(a)# 調(diào)用函數(shù)print("調(diào)用后,a = {}".form at(a))運(yùn)行結(jié)果如下:調(diào)用前,a = [10],引用地址為1782725414080。b 的引用地址為1782725414080。修改后b 的引用地址為1782725414080。調(diào)用后,a = [20]
函數(shù)modifyList()修改形參b的第一個(gè)元素為20,調(diào)用函數(shù)時(shí)傳遞了實(shí)參a,是列表,為可變類型,函數(shù)調(diào)用結(jié)束后,a的第一個(gè)元素被修改成了20。函數(shù)修改了形參的值,導(dǎo)致了實(shí)參值的改變。從運(yùn)行結(jié)果來看,形參b的引用地址和實(shí)參a的引用地址相同,說明函數(shù)調(diào)用時(shí)仍然是把a(bǔ)的引用傳遞給了b,因?yàn)閎為可變類型,所以在b引用指向的內(nèi)存地址中直接修改了這個(gè)值,而沒有重新開辟內(nèi)存空間來保存這個(gè)修改后的值。如圖4所示。當(dāng)傳遞可變類型的實(shí)參時(shí),傳遞的是引用,函數(shù)修改形參的值后,實(shí)參的值也發(fā)生改變[2]。
圖4 傳遞可變類型
對于不可變類型,變量(引用)指向的地址的內(nèi)容是不可變的,改變變量的值只是將變量(引用)指向了新的地址。對于可變類型,變量(引用)指向的地址的內(nèi)容是可變的,改變變量的值就是直接對原地址內(nèi)容的改變。整數(shù)、浮點(diǎn)數(shù)、字符串、布爾類型、元組都是不可變類型,列表、字典、集合是可變類型,在進(jìn)行函數(shù)參數(shù)傳遞時(shí),雖然都是引用傳遞,但要區(qū)分傳遞的是可變類型還是不可變類型。傳遞不可變類型時(shí),改變形參的值,并不會改變實(shí)參的值;傳遞可變類型時(shí),改變形參的值,會改變實(shí)參的值。