python 中 is 和 == 的區別、(不)可變量、tuple和float與其他不可變量的不同

1) is和==的區別:

簡單來說,is和==的區別在於:

is:通過id來判斷兩個對象是否相等,
==:通過value判斷兩個對象的值是否相等。

python中可以將數據類型簡單的分爲兩類:可變量和不可變量。

不可變量:如字符串、範圍在[-5,256]的數值。(is相等只對這兩類有效,float和tuple不能這麼用,後面解釋)
可變量:如列表,字典等。

之前看到這篇文章說,可變和不可變量的區別在於,在id和type相同的情況下,看value是否會變。

2)不可變量:str和int([-5,256])

字符串和範圍內的數值的id在value不做更改的情況下,是不可能變的。

因爲事實上,當它被改變的時候它就被指向了另一個地址。並且,一切需要被傳給另一個變量的對象,它都已經是另一個對象。

比如str的replace,split,upper,他們是複製了一份原來的對象,然後對他們做處理,從而生成另一個對象,他們不是在原對象本身做改變。

>>> a = 'dfdf'
>>> a.upper()
'DFDF'
>>> b = a.upper()
>>> id(a),id(b),id(a.upper())
(50214392L, 46570032L, 46568992L)
>>> id(a),id(a.upper()),id(b)
(50214392L, 46568992L, 46570032L)
>>> 

可以看到,每次對同一個字符upper的id都不一樣。

python的字符串有緩存,如果它們的引用爲0的時候,即原內容不被引用時,如上的upper,會自動銷燬。

3)可變量:list、dict等

而列表與字符串不同,它的列表是一個整體,它的元素如果是通過append等添加更改,id不會變;但是如果你重新賦了一個類似的列表值,id就會發生改變。

>>> a = [123]
>>> id(a)
49980232L
>>> a.append(4)
>>> id(a)
49980232L
>>> a = [1,2,3]
>>> id(a)
49989512L

4)is和==的一般和非一般情況:

對於==沒有什麼意外情況,因爲只要值相等,可變量與不可變量沒有什麼區別,他們都會相等。

而對於is,一般情況下,不可變量,如兩個字符串和數字的id和值都是相等的,用is和None沒有區別(例1和例3)。而對於可變量,如列表,字典等,即便他們的元素個數和值都一樣,他們的id也不會相等(具體例子可以參照例4)。

非一般情況下,以下有幾個例子做對比:

1)

>>> a = 1
>>> b = 1

2)

>>> a = 257
>>> b = 257

3)

>>> a = '1'
>>> b = '1'

4)

>>> a = [1]
>>> b = [1]

5)

>>> a = 1;b = 1

6)

>>> a = 257
>>> b = 257
>>>def test():
       c = 257
       print a is c
   print a is b

分別對他們進行is和==比較:

1)

>>> a = 1
>>> b = 1
>>> a is b
True
>>> a == b
True

2)

>>> a = 257
>>> b = 257
>>> a is b
False
>>> a == b
True

3)

>>> a = '1'
>>> b = '1'
>>> a is b
True
>>> a == b
True

4)

>>> a = [1]
>>> b = [1]
>>> a is b
False
>>> a == b
True

5)

>>> a = 257;b = 257  # 需要在IDLE上進行驗證,在ipython上測試不成功

https://zhuanlan.zhihu.com/p/31169016這篇教程說,同一個代碼塊中的兩個值相同的整數會被重用,意思是is的結果是True。

起初我在ipython上測試並沒有成功,他們的id仍不相同。與緩存無關,我在ipython中測試的,開了兩個窗口,一個該對象已經存在,一個不存在,結果都是一樣的。

後來在python自帶的IDLE中測試成功了,原因是在 Python 的交互式命令行 REPL 中,每單獨一行都視爲一個代碼塊,同一行中的代碼屬於同一個代碼塊,後者重用了前者。

6)

>>> a = 257
>>> b = 257
>>>def test():
       c = 257
       print a is c  # False
>>> a is b # True
>>> test() # False

這個的順序其實是先比較的a和b,再比較a和c。
在py文件中a和b是相等的,但在shell和ipython上都是False。
如上一個鏈接所說,在一個文件中和使用分號一個意思,在一個代碼塊中,屬於模塊級別。而c是局部變量,因此函數裏的257這個對象會被重新創建,而不會和a,b指向同一個位置。

例1和例3就是對不可變量的id值不變的一個證明。例4是對可變量的一個證明。

至於例2,還記得前面提到過的,範圍在[-5,256]的數值。

這裏是因爲在python中,爲了提高性能,所以將一些常用的整數緩存起來,直接從緩存裏拿,而不用每次都去創建。因爲對象在不被引用之後就會被銷燬,如果對於一些非常常用的東西如數字1,每次我們隨手測試都要去創建一個數值爲1的對象,然後再銷燬,就顯得很沒有必要了。而這個整數的範圍就是[-5,256]。

5) 浮點數float

說到整數,就會想起浮點數,它也是不可變量啊,但是因爲浮點數的小數可以有很多,緩存就實在沒有必要了,並且你能選中和它一模一樣的概率不大。

並且它有一個很嚴重的問題,比如下一段代碼,你猜它的結果是什麼?

>>> i = 0.5
>>> while i != 1.0:
    print i
    i += 0.1

你可能和我一樣,覺得它會執行到1.0然後結束循環,但事實上它會進入一個死循環。爲什麼呢?我們把這個while循環手動的去寫一個計算試試:

>>> 0.5+0.1+0.1+0.1+0.1+0.1

你以爲它的結果是1.0,錯了,結果是0.9999……

>>> 0.5+0.1+0.1+0.1+0.1+0.1
0.9999999999999999

這就是爲什麼會死循環的原因,他們根本不會相等啊。

這篇文章給出了原因,大多數的float型數值並不能以精確的形式在計算機中表示出來,只能以十分接近原數值的形式儲存,因此在計算過程中會出現小的誤差。這並不是Python的BUG,而是計算機在表示實際時普遍存在的問題。

6) 元組tuple

而說到不可變量,又會想起tuple,它在創建之後也是不可變的,那麼它算不算不可變量呢?
它的數據類型確實屬於不可變對象,可它的本質卻和數組差不多,和c裏的數組一樣,每次創建都會分配內存空間。
我們想想,tuple真的永遠不變嗎?如果它其中的一個元素是一個列表呢?它確實不能再被編輯,但是你不能阻止它包含的對象是個可變對象併發生改變。


>>> a = (1,)
>>> b = (1,)
>>> id(a),id(b)
(40394648L, 47206528L)

記住了,當你的tuple元素只有一個時,要在末尾加上一個逗號以示區別。如果沒有逗號,你會發現,它其實是個整數類型:

>>> c = (1)
>>> type(c)
int

至於原因,鏈接2裏提到了一個intern機制,於是就去查了一下什麼叫intern機制,可以參考鏈接3

文章裏說道,intern機制是針對字符串的,完整應該叫string intern,即字符串intern機制。

值同樣的字符串對象僅僅會保存一份,是共用的,這也決定了字符串必須是不可變對象。
其實我們想一想,就和數值類型一樣,同樣的數值僅僅要保存一份即可了,不是必須用不同對象來區分。
intern機制的優點是,在創建新的字符串對象時,會先在緩存池裏面找是否有已經存在的值相同的對象(標識符,即只包含數字、字母、下劃線的字符),如果有,則直接拿過來用(引用),避免頻繁的創建和銷燬內存,提升效率。

以上幾段可以解釋字符串和整數的不可變。而tuple沒有intern機制,它就是一個數組,因此它每次都是生成新的對象。

參考:
1. https://zhuanlan.zhihu.com/p/31169016
2. https://blog.csdn.net/vincent_ceso/article/details/76473834
3. https://blog.csdn.net/june_young_fan/article/details/79750589

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章