小心Python中的 “is” | 第80期

Python 的很多語法用起來都非常自然,比如“is”,但與這個“is”相關的語法也有一些坑。

特別是在與字符串一起使用時,由於解釋器的“駐留機制”,常常造成一些匪夷所思的現象。雖然也並不會造成特別嚴重的事故,但知曉其中的原理之後,確實能夠避免一些不必要的“踩坑”經歷。

1 一個眾說紛紜的案例

“is”與字符串聯合使用的"坑"得從一段眾說紛紜的程序運行結果說起。首先,我們來看一段非常合理的程序

第一段程序

<code>a = "some_string"
b = "some" + "_" + "string"
print(a is b)/<code>

大家可以想一想,這段程序會輸出什麼呢?如果你是解釋器的設計者,你會不會認為 a is b 應該是 True 呢?有一些人認為不應該,因為這畢竟是兩個對象啊;而我們大部分人都認為是應該的,畢竟確實是一致,這一點並不奇怪。事實上,Python解釋器也是這麼做的,即以上代碼在 Python3.7 的解釋器下獲得的結果是 “True”。這裡之所以要強調 Python 的版本,是因為無法保證不同版本的解釋器能得到相同的結果,並且以下的運行結果都是在 Python3.7 中運行的。

那麼下面一段代碼是不是也會輸出 “True” 呢?

第二段程序

<code>c = "hello"
d = "hello"
print(c is d)/<code>

如果我們認為第一段代碼應該輸出 “True” 的話,那麼第二段代碼輸出 “True” 就是自然而然的事情了。我們在 Python3.7 中實驗也確實得到我們想要的結果 “True”。到這裡似乎都很正常,也沒有什麼所謂的“坑”,但是,看了下面的一段代碼,可能你的世界觀就坍塌了。

第三段程序

<code>e = "hello!"
f = "hello!"
print(e is f)/<code>

沿用 a is b 和 c is d 都是 “True” 的實驗結果,我們認為 e is f 肯定也必須是 “True”吧?但實際情況讓我們大跌眼鏡,第三段程序居然在 Python3.7 中輸入 “False”。到這裡,我們就疑惑了,第二段與第三段程序的差別只有一點,即第三段的字符串多了 “!” ,難道 Python 解釋器語法與數據的內容有關?如果沒法洞悉其中的秘密,我們先記下這個結果,再看第四段程序。

第四段程序

<code>g,h = "hello!", "hello!"
print(g is h)/<code>

按照第三段程序的邏輯,第四段程序應該輸出 “False” 吧?在 Python3.7 中確實如此,輸出了 “False”。但是這個事情並沒有結束,因為有人在 Python3.8 中跑出的結果是 “True”。到這裡,我們不得不冷靜下來想一想,這些語句之間的區別和聯繫,也不得不想一想 Python 中 “is” 關鍵字的實現原理。

2 “is”的原理

我們常常使用 if x is True 或者 if y is False 類似這樣的語句來進行條件判斷,我們以為 “is” 是通過判斷兩個變量或者變量與常量的內容是否一致來實現的,但是事實上並非如此。“is” 是通過判斷對象的對象的邏輯地址是否一致來工作的。

對 Python 稍微瞭解一些的朋友大概都知道,我們沒有辦法像 “C/C++” 那樣直接修改內存地址指向的內容,但這並不能說明 Python 中沒有地址。實際上,Python 的每一個對象都有對應的 “邏輯地址”。這個地址可以使用 id() 函數傳入對象獲取,但是該地址的用處僅僅是判斷對象是否相同,而沒有其他用途。

“is” 關鍵字,正是通過 id() 函數獲取到對象的 “邏輯地址”, 如果地址一致,則返回 “True”;不一致,則返回 “False”。我們可以使用 id() 函數一一打印上面4段程序中的8個字符串對象,發現,“is” 的判斷結果為 “True” 的一組對象的 “邏輯地址” 相同。

小心Python中的 “is” | 第80期

"is" 工作原理

也就是說,上面四段程序中,在 Python3.7裡面,“a-b”是在同一個地址,“c-d”也是在同一個地址;而"e-f"的地址不相同,"g-h"的地址也不相同。這就有點詭異呢?第一,Python為什麼要把不同對象放在同一個地址呢?為什麼又不是所有具有相同內容的對象都放到同一個地址呢?關於這些疑惑就需要 “駐留機制” 來解釋了。

3 駐留機制

所謂 “駐留機制” 是指,解釋器或者編譯器為了節省內存,而將具有符合某種規 則的相同內容的對象放在同一塊地址中。Python 的 “駐留機制” 比較複雜,這裡只說明其中關鍵的一點,即判斷字符串是否駐留的規則是:內容符合編程語言要求的命名規則,即由字母、數字或者下劃線這三種字符所組成的字符串才會進行駐留。

小心Python中的 “is” | 第80期

字符串駐留機制

這樣看來,那4段程序中的 a b c d都是符合命名規則的,e f g h都是不符合命名規則的。那些詭異的實驗現象也就得到了一個比較合理的解釋。

到了這裡,可能有人還記得,第4段程序在 Python3.8 下輸出的可是 “True” 啊!這是赤裸裸地告訴我們,不同版本解釋器之間的實現行為存在差異。

4 一點建議

從關於這4段程序的輸出結果,到關於 “is” 的實現原理和 “駐留機制” 的解釋,我們似乎以為自己對這種詭異現象瞭如指掌。但是在兩個不同版本解釋器上運行結果的不一致再次對我們發佈警告,也就是這裡給 Python 使用者的一點建議。

不要對字符串的判斷使用 “is”

忘記 “駐留機制”,永遠也別打算利用這種機制

不要了解太多的Python底層,因為了解太多就可能在不知不覺中使用

日常使用,或許不會遇到那些詭異的“坑”,但多瞭解一些總是更有底氣一點。如果能將這幾條建議變成習慣,那就不用考慮那麼多了。

----------------------

題外:

頻道資源,可以私信關鍵字獲取。

Python編程問題諮詢,請發送關鍵字【諮詢】

獲取leetcode源代碼,請發送關鍵字【leetcode】

獲取書籍,請發送關鍵字【書籍】


分享到:


相關文章: