x is y 和 x == y 在 Python 裡問的是兩個不同的問題,只是拿整數來測的時候答案常常一樣,於是很容易被當成同一件事。== 比的是值,is 比的是身分,也就是兩個名字後面是不是同一個物件。
文件上寫,is 是「test for an object’s identity」,x is y「is true if and only if」x 和 y「are the same object」,而是不是同一個物件,由 id() 決定(文件)。== 走的是另一條路,它比的是兩個物件的值相不相等,值相等的兩個東西,不保證是同一個物件。
分界到底在哪,直接在直譯器裡跑一下就清楚了(以下都是 CPython 3.11):
>>> 256 is int("256")
True
>>> 257 is int("257")
False
>>> 257 == 257
True
== 永遠回傳 True,因為 257 就是等於 257,值一樣。但 is 在 256 上是 True、257 上是 False,分界剛好落在 256 和 257 之間。
原因是 CPython 的小整數快取。C API 的文件寫著,CPython「keeps an array of integer objects for all integers」,範圍是 -5 到 256,在這個範圍裡「just get back a reference to the existing object」(文件)。也就是說,256 這個物件在直譯器啟動時就已經建好放著了,每次寫 256、或用 int("256") 在執行期算出 256,拿到的都是那同一個物件,is 就成立。257 不在快取範圍內,每次要用就新建一個,兩個 257 是各自獨立的物件,身分自然不同。
這裡有個地方特別容易讓人測到看似矛盾的結果。如果在同一個函式裡直接寫兩個字面值:
>>> def f():
... a = 257
... b = 257
... return a is b
...
>>> f()
True
a is b 回傳 True,看起來好像 257 也被快取了,其實不是。這是常數摺疊,編譯器發現函式裡兩個 257 是同一個常數,就只存一份,a 和 b 指向的是同一個編譯期常數,跟小整數快取沒關係。同一個字面值多次求值「may obtain the same object or a different object with the same value」(文件)。
那個很有名的「257 is 257 回傳 False」示範會出問題,就是這個原因,結果時而 True 時而 False,取決於兩個 257 是不是同一份摺疊後的常數。想看到真正的快取邊界,就得用 int("257") 這種執行期才算出來的整數,繞過常數摺疊。
順帶一提,目前的 CPython 如果你直接寫 x is 257,直譯器會丟出 SyntaxWarning: "is" with a literal. Did you mean "=="?。
-5 到 256 這個範圍從來就不是保證,只是目前的實作。開發中的 main 分支已經把上界改成 1024。哪天邊界換了個版本就變了,靠 is 去比整數值的程式碼會默默壞掉,也不會有錯誤訊息提醒你。
所以實務上規則很簡單:要比整數值、或任何一般物件的值,用 ==。is 留給判斷是不是同一個物件,最典型的就是跟 None 這種單例比:
>>> x = None
>>> x is None
True
PEP 8 也是這樣建議,「Comparisons to singletons like None should always be done with」is 或 is not,「never the equality operators」(PEP 8)。None 整支程式只有一個,拿身分去比它最直接也最快。整數值不屬於這一類,它只是剛好落在 is 對得上的那段範圍裡。
這篇簡單的分享,可能有錯、若發現會盡快修改~



