Python では == と is はまったく異なる比較を行う。== は値の等価性を、is はオブジェクトの同一性を比較する。この違いを正しく理解しないと、特に CPython の整数キャッシュなどの最適化により、予期せぬバグに遭遇することがある。
基本的な違い
== と is の違いを確認しよう。
a = [1, 2, 3] b = [1, 2, 3] c = a # == は値の等価性を比較 print(a == b) # True(内容が同じ) print(a == c) # True # is はオブジェクトの同一性(アイデンティティ)を比較 print(a is b) # False(別のオブジェクト) print(a is c) # True(同じオブジェクト) # id() で確認 print(id(a), id(b), id(c)) # 例: 140234567890 140234567891 140234567890
is は内部的に id() の値を比較している。同じメモリアドレスを指しているかどうかを確認するわけだ。
None との比較は is を使う
None は Python でシングルトン(唯一のインスタンス)として実装されている。そのため、None との比較には is を使うのが推奨される。
value = None # 推奨 if value is None: print("value is None") if value is not None: print("value is not None") # 非推奨(動作するが) if value == None: print("value equals None")
== でも動作するが、__eq__ がオーバーライドされたクラスでは予期せぬ結果になる可能性がある。
class Weird: def __eq__(self, other): return True # 何と比較しても True w = Weird() print(w == None) # True(意図しない結果) print(w is None) # False(正しい結果)
CPython の整数キャッシュ
CPython は、-5 から 256 までの整数をあらかじめキャッシュしている。この範囲の整数は、同じ値なら同じオブジェクトを参照する。
# キャッシュ範囲内(-5 〜 256) a = 256 b = 256 print(a is b) # True(同じオブジェクト) # キャッシュ範囲外 a = 257 b = 257 print(a is b) # False(異なるオブジェクト) # 負の数も同様 a = -5 b = -5 print(a is b) # True a = -6 b = -6 print(a is b) # False
この挙動は実装の詳細であり、仕様ではない。他の Python 実装(PyPy など)では異なる場合がある。
文字列のインターニング
CPython は特定の文字列も最適化のためにインターン(再利用)する。
# 短い文字列や識別子的な文字列はインターンされる a = "hello" b = "hello" print(a is b) # True(多くの場合) # 連結で作成した文字列 c = "hel" + "lo" print(a is c) # True(コンパイル時に最適化される) # 実行時に作成した文字列 d = "".join(["h", "e", "l", "l", "o"]) print(a is d) # False(実行時に作成されるため) # 文字列の値としては等しい print(a == d) # True
スペースや特殊文字を含む文字列は通常インターンされない。
a = "hello world" b = "hello world" print(a is b) # False(スペースを含むため) print(a == b) # True a = "hello!" b = "hello!" print(a is b) # False(! を含むため) print(a == b) # True
なぜ is を値の比較に使ってはいけないのか
整数キャッシュや文字列インターニングの挙動に依存すると、以下のような問題が起きる。
def is_special_number(n): # 悪い例:is で比較 return n is 256 print(is_special_number(256)) # True print(is_special_number(256 + 0)) # True(キャッシュ範囲内) def is_large_number(n): return n is 1000 print(is_large_number(1000)) # 結果は実装依存! # 同じソースファイル内で定数として定義されていれば True になることも
# さらに危険な例 x = 257 y = 257 # 同じ行で定義すると最適化される場合がある a = 300; b = 300 print(a is b) # True になることも(同じコードブロック内の最適化) # 対話モード(REPL)では異なる結果になることも
正しい使い分け
値の等価性を比較したいとき。数値、文字列、リスト、辞書などの内容を比較する場合。
オブジェクトの同一性を確認したいとき。None、True、False との比較。シングルトンパターンの実装。
# 正しい使い分け value = get_something() # None チェック:is を使う if value is None: handle_none() # 値のチェック:== を使う if value == 0: handle_zero() # ブール値のチェック:is を使うことが推奨される場合もある if value is True: # 厳密に True かどうか pass if value: # Truthy かどうか(これが一般的) pass
真偽値と is
True と False もシングルトンだが、is での比較は注意が必要だ。
# True/False は is で比較できる print(True is True) # True print(False is False) # True # しかし、bool 以外の Truthy/Falsy 値との比較は危険 print(1 == True) # True(int と bool の値比較) print(1 is True) # False(異なるオブジェクト) print(0 == False) # True print(0 is False) # False
通常は if value: や if not value: のように、暗黙の真偽値変換を使う方がよい。
まとめ
== と is の使い分けは明確だ。
値を比較するなら ==、同一オブジェクトかどうかを確認するなら is。CPython の整数キャッシュや文字列インターニングに依存してはいけない。
None との比較は is を使う。数値や文字列の比較は == を使う。シングルトンパターン以外では is による値比較を避ける。
この使い分けを守れば、実装依存のバグを避けられる。