Python中的不可變對象類型與可變對象類型

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

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

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

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中的變量都是指針,這確實和以前學過的強類型語言是有不一樣的。由於變量是指針,因此全部的變量無類型限制,能夠指向任意對象。指針的內存空間大小是與類型無關的,其內存空間只是保存了所指向數據的內存地址。spa

Python 的全部變量其實都是指向內存中的對象的一個指針,全部的變量都是!.net

此外,對象還分兩類:一類是可修改的,一類是不可修改的。個人理解是把可修改(mutable)的類型叫作值類型,不可修改(immutable)類型叫作引用類型。指針

3. 對象

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

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

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

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

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方法,不然計數器回收,沒法解決循環引用的問題。

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

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

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

相關文章
相關標籤/搜索