is vs == の判断基準と CPython の整数キャッシュ(Python)
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 140234567890is は内部的に 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 による値比較を避ける。
この使い分けを守れば、実装依存のバグを避けられる。



