Python - 10 is 10 vs 500 is 500

在使用 Python 進行程式設計時,可能會觀察到一種特殊的現象:對於特定範圍內的整數,使用 is 運算子比較兩個賦予相同值的變數會返回 True,而超出該範圍的整數則返回 False

具體示例如下:

1
2
3
4
5
6
7
8
9
>>> a = 10  
>>> b = 10
>>> a is b
True # 嗯,合理!

>>> x = 500
>>> y = 500
>>> x is y
False # WTF???

當變數 ab 均被賦值為 10 時,a is b 的評估結果為 True。然而,當變數 xy 被賦值為 500 時,x is y 的評估結果卻為 False,儘管其值相等。此現象並非程式錯誤,而是源於 Python 內部的一種最佳化機制

本文將扼要地闡釋此最佳化機制背後的原理。

運算子 is 與 == 之區別

首先,我們必須釐清 Python 中兩個重要的比較運算子:is==

  • == (等於):這個運算子檢查的是兩個變數所指向的值 (Value) 是否相等。這是我們最常用來比較數值、字串等內容是否相同的方式。

    1
    2
    3
    4
    5
    6
    >>> 10 == 10  
    True
    >>> 500 == 500
    True
    >>> "hello" == "hello"
    True
  • is (是):這個運算子檢查的是兩個變數是否指向記憶體中完全相同的物件(Object)。它比較的是物件的識別碼(Identity)。你可以把它想像成檢查兩個變數是不是指向同一個「實體」。我們可以用內建的 id() 函數來查看物件在記憶體中的唯一識別碼(在 CPython 中通常是記憶體位址)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    >>> a = [1, 2]  
    >>> b = [1, 2]
    >>> c = a
    >>> a == b # 值相等
    True
    >>> a is b # 但不是同一個物件
    False
    >>> a is c # a 和 c 指向同一個物件
    True
    >>> id(a) == id(b)
    False
    >>> id(a) == id(c)
    True

Python 的整數快取機制 (Integer Caching)

釐清 is== 的功能後,即可理解 500 is 500False 的原因:Python 為這兩個值分別建立了獨立的物件實例。

然而,10 is 10 為何結果為 True

答案在於 整數快取 (Integer Caching),亦稱為 小整數物件池 (Small Integer Object Pool)。

此機制為 Python(尤其是標準實作 CPython)為提升執行效能與記憶體利用率而採用的一種最佳化策略:
https://docs.python.org/3/c-api/long.html#c.PyLong_FromLong

  1. 預先實例化: CPython 在啟動階段會預先建立並儲存一個常用小範圍整數的物件陣列,形成一個快取池。
  2. 範圍: 這個範圍通常是 -5 到 256 (包含 -5, 256)。
  3. 重複使用: 當程式碼中需要使用此範圍內的整數時,Python 將直接從快取池中返回對應的已存在物件,而非重複建立新物件。

案例分析

現在,讓我們用整數快取的概念來解釋一開始的現象:

  • a = 10, b = 10:
    • 10 落在 -5 到 256 的快取範圍內。
    • 執行 a = 10 時,a 指向快取池中那個代表 10 的物件。
    • 執行 b = 10 時,Python 發現快取池中已經有 10 了,於是讓 b 也指向同一個物件。
    • 因為 ab 指向的是記憶體中完全相同的物件,所以 a is bTrue。 (id(a) 會等於 id(b))
  • x = 500, y = 500:
    • 500 不在 -5 到 256 的快取範圍內。
    • 執行 x = 500 時,Python 必須在記憶體中建立一個新的物件來代表 500。
    • 執行 y = 500 時,Python 再次建立另一個新的物件來代表 500。
    • 雖然 xy 的值都是 500 (x == y 為 True),但它們是記憶體中兩個獨立的物件。因此,x is yFalse。 (id(x) 不等於 id(y))

結論與建議

  • == 比較is 比較物件識別碼 (是否為同一個物件)。
  • CPython 會快取 -5 到 256 之間的整數物件以進行最佳化。
  • 永遠使用 == 來比較數值是否相等。
  • is 主要用於檢查變數是否指向同一個物件實例,最常見的用途是檢查變數是否為 None (variable is None)。

需要注意的是,整數快取行為屬於 CPython 的實作細節 (implementation detail)。雖然 -5256 這一範圍在多數版本中保持穩定,但理論上無法保證在所有 Python 實作或未來版本中維持不變。因此,為確保程式碼的健壯性與可移植性,不應依賴 is 運算子進行數值比較。

本文旨在釐清 Python 中整數比較所觀察到的特殊行為及其背後的機制。理解此差異有助於撰寫更精確且可靠的 Python 程式碼。

也許你也會想看看