1. 程式人生 > >Python中的值類型與引用類型

Python中的值類型與引用類型

我們 語言 bsp com 整型 最終 RoCE 錯誤 回收

https://blog.csdn.net/answer3lin/article/details/86430074

其實各個標準資料中沒有說明Python有值類型和引用類型的分類,這個分類一般是C++和Java中的。但是語言是相通的,所以Python肯定也有類似的。實際上Python 的變量是沒有類型的,這與以往看到的大部分語言都不一樣(JS等弱類型的也是這樣)。但 Python 卻是區分類型的,那類型在哪裏呢?事實是,類型是跟著內存中的對象走的。類型屬於對象變量是沒有類型的。一般也分實參和形參。

《learning python》中的一個觀點:變量無類型,對象有類型

技術分享圖片

1. 對象類型

不可變(immutable)對象類型


int
float
decimal
complex
bool
str
tuple
range
frozenset
bytes
可變(mutable)對象類型
list
dict
set
bytearray
user-defined classes (unless specifically made immutable)

2. 變量

Python中的變量都是指針,這確實和之前學過的強類型語言是有不同的。因為變量是指針,所以所有的變量無類型限制,可以指向任意對象。指針的內存空間大小是與類型無關的,其內存空間只是保存了所指向數據的內存地址。

Python 的所有變量其實都是指向內存中的對象的一個指針,所有的變量都是!

此外,對象還分兩類:一類是可修改的,一類是不可修改的。

我的理解是把可修改(mutable)的類型叫做值類型,不可修改(immutable)類型叫做引用類型。

3. 對象

對象=確定內存空間+存儲在這塊內存空間中的值。

Java中,對象是分配在堆上的,存儲真正的數據,而引用是在棧中開辟的內存空間用於引用某一個對象(值類型的變量也是存儲到棧上)。

4. 值類型(不可變對象)

在Python中,數值(整型,浮點型),布爾型,字符串,元組屬於值類型,本身不允許被修改(不可變類型),數值的修改實際上是讓變量指向了一個新的對象(新創建的對象),所以不會發生共享內存問題。 這種方式同Java的不可變對象(String)實現方式相同。原始對象被Python的GC回收。

a = 1
b = a
a = 2
print(b)  #輸出的結果是1 修改值類型的值,只是讓它指向一個新的內存地址,並不會改變變量b的值。

 1 >>> x = 1
 2 >>> id(x)
 3 31106520
 4 >>> y = 1
 5 >>> id(y)
 6 31106520
 7 >>> x = 2
 8 >>> id(x)
 9 31106508
10 >>> y = 2
11 >>> id(y)
12 31106508
13 >>> z = y
14 >>> id(z)
15 31106508
16 >>> x += 2
17 >>> id(x)
18 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類型的變量再次賦值,在內存中相當於又創建了一個新的對象,而不再是之前的對象。從下圖中就可以看到上面程序的過程。

技術分享圖片

備註:圖片中錯誤。x=2,y=2,z=y。

從上面的過程可以看出,不可變數據類型的優點就是內存中不管有多少個引用,相同的對象只占用了一塊內存,但是它的缺點就是當需要對變量進行運算從而改變變量引用的對象的值時,由於是不可變的數據類型,所以必須創建新的對象,這樣就會使得一次次的改變創建了一個個新的對象,不過不再使用的內存會被垃圾回收器回收。

5. 引用類型(可變類型)

在Python中,列表,集合,字典是引用類型,本身允許修改(可變類型)。

1 list_a = [1,2]
2 list_b = list_a
3 list_a[0] = 3
4 print(list_b)  #此時的輸出結果是[3,2]

修改引用類型的值,因為list_b的地址和list_a的一致,所以也會被修改。

 1 >>> a = [1, 2, 3]
 2 >>> id(a)
 3 41568816
 4 >>> a = [1, 2, 3]
 5 >>> id(a)
 6 41575088
 7 >>> a.append(4)
 8 >>> id(a)
 9 41575088
10 >>> a += [2]
11 >>> id(a)
12 41575088
13 >>> a
14 [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、+=等這種操作。

6. 不可變的例外

並非所有的不可變對象都是不可變的。

如前所述,Python容器比如元組,是不可變的。這意味著一個tuple的值在創建後無法更改。但是元組的“值”實際上是一系列名稱,它們與對象的綁定是不可改變的。關鍵點是要註意綁定是不可改變的,而不是它們綁定的對象。

讓我們考慮一個元組t =(‘holberton‘,[1,2,3])

上面的元組t包含不同數據類型的元素,第一個元素是一個不可變的字符串,第二個元素是一個可變列表。元組本身不可變。即它沒有任何改變其內容的方法。同樣,字符串是不可變的,因為字符串沒有任何可變方法。但是列表對象確實有可變方法,所以可以改變它。這是一個微妙的點,但是非常重要:不可變對象的“值” 不能改變,但它的組成對象是能做到改變的。

其實主要原因是元組內保存的是變量(也就是內存地址)。所以當變量指向對象發生變化時,如果導致變量發生變化(即不可變類型),此時元組保證該不可變類型不能修改。而如果當變量指向對象發生變化時,如果不會導致變量發生變化(即可變類型),此時元組中存儲的該可變類型可以修改(因為變量本身並無變化)

7. 總結

python中的不可變數據類型,不允許變量的值發生變化,如果改變了變量的值,相當於是新建了一個對象,而對於相同的值的對象,在內存中則只有一個對象,內部會有一個引用計數來記錄有多少個變量引用這個對象;

可變數據類型,允許變量的值發生變化,即如果對變量進行append、+=等這種操作後,只是改變了變量的值,而不會新建一個對象,變量引用的對象的地址也不會變化,不過對於相同的值的不同對象,在內存中則會存在不同的對象,即每個對象都有自己的地址,相當於內存中對於同值的對象保存了多份,這裏不存在引用計數,是實實在在的對象。

8. 參數傳遞

C++中是傳值和傳引用(指針)。c語言加上*號傳遞指針就是引用傳遞,而直接傳遞變量名就是值傳遞)

Java是傳值(傳值和傳引用,只不過引用就是內存地址,所以也是值)。Java裏區分值和引用,是因為值存儲在棧裏,而引用對象存儲在堆裏(引用本身在棧裏)。

而Python所有的都是對象,都是引用,所以所謂的值類型都是不可變類型。類似於Java的字符串類型。

所以Python中的參數傳遞都是傳遞引用,也就是傳遞的是內存地址。只不過對於不可變類型,傳遞引用和傳遞值沒什麽區別。而對於可變類型,傳遞引用是真的傳遞內存的地址。

聽說python只允許引用傳遞是為方便內存管理,因為python使用的內存回收機制是計數器回收,就是每塊內存上有一個計數器,表示當前有多少個對象指向該內存。每當一個變量不再使用時,就讓該計數器-1,有新對象指向該內存時就讓計數器+1,當計時器為0時,就可以收回這塊內存了。當然還有其他的GC方法,否則計數器回收,無法解決循環引用的問題。

值傳遞: 表示直接傳遞變量的值,把傳遞過來的變量的值復制到形參中,這樣在函數內部的操作不會影響到外部的變量。

引用傳遞: 把引用理解為變量(標識符)與數據之間的引用關系,標識符通過引用指向某塊內存地址。而引用傳遞,傳遞過來的就是這個關系,當你修改內容的時候,就是修改這個標識符所指向的內存地址中的內容,因為外部也是指向這個內存中的內容的,所以,在函數內部修改就會影響函數外部的內容。

另外:列表,字典,集合是容器類型,嵌套引用。

技術分享圖片

Python中的值類型與引用類型