# Python 的 String Interning 機制

> 介紹 Python 中的 String Interning 機制，說明變數比較運算子 is 與 == 的核心差異。文章內容涵蓋 id() 函數的使用方法、字串在記憶體中的存放邏輯，以及 sys.intern() 的手動調用方式。同時說明 Python 對於小整數負 5 到 256 的緩存處理，幫助開發者理解 Python 內部如何優化記憶體使用並避免常見的物件比較陷阱。

Published: 2024-02-27
URL: https://kaochenlong.com/string-interning-in-python

---

許多程式語言會使用 2 個等號 `==` 比較兩個值是否相等，在 Python 也是一樣，但 Python 還有個特別的比較方法 `is`，它可以用來比較這兩個值是不是同一個物件：

&lt;!-- more --&gt;

```py
&gt;&gt;&gt; a = [1, 2, 3]
&gt;&gt;&gt; b = [1, 2, 3]
&gt;&gt;&gt; a == b
True
&gt;&gt;&gt; a is b
False
```

雖然變數 `a` 跟 `b` 看起來都是串列 `[1, 2, 3]`，但實際上它們在 Python 是兩個不同的東西，你可以想像成我們從網路上買了兩盒一樣的玩具，如果沒有特別的碰撞或破損的話，這兩盒玩具的包裝盒看起來都一樣，裡面的玩具看起來也是一樣，但這兩個玩具本質上就是兩個不同的玩具。在 Python 有個叫做 `id()` 的內建函數，可以用來查看某個東西在 Python 世界裡的「編號」：

```py
&gt;&gt;&gt; a = [1, 2, 3]
&gt;&gt;&gt; b = [1, 2, 3]
&gt;&gt;&gt; id(a)
4303554880
&gt;&gt;&gt; id(b)
4303556672
```

可以看的出來這兩個串列雖然看起來一樣，但內部的編號是不同的。再來給大家看一個例子：

```py
&gt;&gt;&gt; a = [1, 2, 3]
&gt;&gt;&gt; b = a
&gt;&gt;&gt; a == b
True
&gt;&gt;&gt; a is b
True
```

第二行的 `b = a` 的意思，就是把 `a` 的值指派給 `b`，所以這時候不管是 `a` 或 `b` 都是指向同一個地方，它們的 `id()` 函數的執行結果也會一樣，所以 `a is b` 會是 `True`。到這裡應該還算容易理解，接著來看比較難一點的：

```py
a = &quot;hellokitty&quot;
b = &quot;hellokitty&quot;
print(a == b) # 印出什麼？
print(a is b) # 印出什麼？
```

猜猜看，你覺得會印出什麼結果？`==` 的比較容易猜，就是比對這兩個變數的值是不是一樣，所以會得到 `True`。而 `is` 會比較這兩個 `&quot;hellokitty&quot;` 字串是不是同一個，這在其它的程式語言可能會有不同的結果，我用 Ruby 舉個例子：

```rb
&gt;&gt; a = &quot;hellokitty&quot;
&gt;&gt; b = &quot;hellokitty&quot;
&gt;&gt; a.object_id
=&gt; 47540
&gt;&gt; b.object_id
=&gt; 56940
```

這裡的 `.object_id` 方法跟剛剛提到的 `id()` 函數的功能差不多，在 Ruby 裡的字串雖然看起來一樣，但它們實際上在記憶體裡就是兩個不同的東西。這在 Python 裡會有不同的結果，還記得我們在上個章節才講到 Python 的字串本身的設計是不可變（Immutable）的嗎？既然不能變，所以 Python 內部會建立一個「表格」用來存放一些字串，試著盡可能的把同樣的字串建放同一個地方，以節省一些記憶體空間，這個機制叫做 String Interning（有興趣去翻 Python 的原始碼就會發現這個「表格」本質上是一個字典）。當你試著把一個字串被被命名為某個變數的時候，Python 會決定要不要使用之前曾經使用過的字串。

```py
&gt;&gt;&gt; id(&quot;hellokitty&quot;)
4305320112
&gt;&gt;&gt; id(&quot;hellokitty&quot;)
4305320112
&gt;&gt;&gt; id(&quot;hellokitty&quot;)
4305320112
```

但也不是所有的字串都會走這個機制，例如這個例子：

```py
&gt;&gt;&gt; id(&quot;hello kitty&quot;)
4305197104
&gt;&gt;&gt; id(&quot;hello kitty&quot;)
4305197424
&gt;&gt;&gt; id(&quot;hello kitty&quot;)
4305195568
```

String Interning 機制主要會發生在幾個情況，只有字數比較少而且只包含英文字母、數字、底線的字串才會幫我們處理，在上面這個範例中的字串有空白字元，Python 就沒有幫我們保留下來，而是每次都會產生一個新的字串。如果你想要強制讓 Python 發動 String Interning 機制的話，可使用內建模組 `sys` 的 `intern()` 函數：

```py
&gt;&gt;&gt; import sys
&gt;&gt;&gt; a = sys.intern(&quot;hello kitty&quot;)
&gt;&gt;&gt; b = sys.intern(&quot;hello kitty&quot;)
&gt;&gt;&gt; a is b
True
```

不只字串，數字也有類似的機制，但這個機制不會無限上綱到所有的數字，只有在 `-5` 到 `256` 之間的整數才會發生：

```py
&gt;&gt;&gt; a = 256
&gt;&gt;&gt; b = 256
&gt;&gt;&gt; a is b
True

# 超過範圍了
&gt;&gt;&gt; a = 257
&gt;&gt;&gt; b = 257
&gt;&gt;&gt; a is b
False
```

因為 -5 到 256 之間的數字很常用到，所以 Python 在啟動的時候有先幫我們就把這些數字先準備好，如果在程式裡用到的話就不用另外重新產生一份。


