Pythonでは同じ見た目の値でも、メモリ上のオブジェクトが同一かどうかと、値として等しいかどうかは別の概念です。
本記事ではisと==の違いを軸に、Noneや小さな数値・文字列で起きがちな混乱と安全な使い分けを、動作確認コードとともに丁寧に解説します。
Pythonのisと==の違い(同一性と同値)
isはオブジェクトの同一性(identity)を比較
isは「同じオブジェクトか」を判定します。
アドレス(実体)が同じならTrueです。
見た目が同じ値でも、別々に作られたオブジェクトならFalseになります。
# is は「同一オブジェクトか」を見る
a = [1, 2]
b = a # b は a と同じリストを参照
c = [1, 2] # c は内容は同じだが、別オブジェクト
print("a is b:", a is b) # 同じオブジェクトを参照しているので True
print("a is c:", a is c) # 別オブジェクトなので False
a is b: True
a is c: False
==は値の同値(equality)を比較
==は「値として等しいか」を判定します。
オブジェクトが別でも、値が等しければTrueです。
クラスが__eq__
を実装している場合は、その定義が使われます。
# == は「値として等しいか」を見る
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def __eq__(self, other):
# 値(座標)が一致すれば等しいとみなす
return isinstance(other, Point) and self.x == other.x and self.y == other.y
p1 = Point(1, 2)
p2 = Point(1, 2) # 別インスタンス
print("p1 is p2:", p1 is p2) # 別オブジェクトなので False
print("p1 == p2:", p1 == p2) # 値が同じなので True
p1 is p2: False
p1 == p2: True
id()で確認できるisの挙動
id関数は「オブジェクトの同一性」を表す整数を返します。
a is b
がTrueなら、id(a) == id(b)
もTrueになります。
a = [1, 2]
b = a
c = [1, 2]
print("id(a) == id(b):", id(a) == id(b)) # True
print("id(a) == id(c):", id(a) == id(c)) # False
id(a) == id(b): True
id(a) == id(c): False
Noneの比較はisが正解
x is None / x is not None がベストプラクティス
Noneは唯一のシングルトンです。
PEP 8でもx is None
やx is not None
を推奨しています。
def find_user(user_id):
# 見つからないケースを None で表現する関数だとする
return None
user = find_user(123)
print("user is None:", user is None) # ベストプラクティス
print("user == None:", user == None) # 動くが推奨されない(可読性・静的解析の観点)
user is None: True
user == None: True
==でNone比較を行うと、ユーザー定義型の__eq__が介入する余地が生まれるため、意図せぬ挙動につながる可能性があります。
True/Falseの比較にisは使わない
ブール比較でis True
やis False
は書かないのが実務の定石です。
単に条件式の真偽で判定します。
flag = (3 > 1)
# 悪い例: 実装依存の型(例: numpy.bool_)では壊れる可能性
print("flag is True:", flag is True)
# 良い例: 真理値として判定する
if flag:
print("OK")
# False を明示したいとき
if not flag:
print("NG")
flag is True: True
OK
- 関連記事:真偽値boolとは?True/Falseの基本と使い方
- 関連記事:if文の基本と書き方をサンプルコード付きで解説
- 真理値の判定(標準型の真理値テスト) — Python 3.13.7 ドキュメント
シングルトン(None, Ellipsis, NotImplemented, 自作sentinel)はis
同一性で意味が決まるシングルトンはisを使います。
組み込みではNone
、Ellipsis
(...
)、NotImplemented
などが該当します。
自作センチネルもis
で判定します。
# 組み込みシングルトンの比較
print("Ellipsis is ...:", Ellipsis is ...)
print("NotImplemented is NotImplemented:", NotImplemented is NotImplemented)
# 自作センチネルの活用
_sentinel = object()
def f(x=_sentinel):
if x is _sentinel: # 引数が渡されなかったことを厳密に検出
return "no argument"
if x is None: # None が明示的に渡された
return "got None"
return f"got {x}"
print(f())
print(f(None))
print(f(10))
Ellipsis is ...: True
NotImplemented is NotImplemented: True
no argument
got None
got 10
- 定数: None / Ellipsis / NotImplemented
- 関連記事:デフォルト引数とは? 引数に初期値を設定する方法
- 関連記事:なぜ危険?Pythonでデフォルト引数にリストや辞書を使うな
小さな数・文字列でisが通る理由と落とし穴
小整数のキャッシュでisが成立することがある(CPython)
CPythonは一部の小整数(一般に-5〜256)を事前にキャッシュします。
そのため、この範囲ではis
がTrueになることがあります。
しかし、仕様ではなく実装依存の最適化です。
# 小整数キャッシュの例 (CPython で観測されやすい)
a = 256
b = 256
print("256 is cached:", a is b) # 多くの環境で True
# 大きな整数は通常キャッシュされないので is は False になりやすい
c = int("257") # 動的生成でキャッシュの影響を避ける
d = int("257")
print("257 is same object:", c is d) # ほぼ確実に False
# 値として等しいかは ==
print("257 equals:", c == d) # True
256 is cached: True
257 is same object: False
257 equals: True
整数の比較は必ず==を使ってください。
x is 0やx is 1はアンチパターンです。
文字列のインターンでisが成立することがある
インタプリタは一部の文字列をインターン(共有)します。
リテラルや識別子的な文字列は同じオブジェクトを共有することがあり、is
がTrueになる場合があります。
動的に生成した文字列は別オブジェクトになりやすいです。
import sys
a = "python"
b = "python"
print("literal is:", a is b) # True になりやすい(実装や状況に依存)
c = "".join(["py", "thon"])
print("a == c:", a == c) # 値は等しい
print("a is c:", a is c) # 別オブジェクトになりやすい
# sys.internで手動インターンすれば同一オブジェクトにできる
e = sys.intern("python")
f = sys.intern("".join(["py", "thon"]))
print("interned is:", e is f) # True
literal is: True
a == c: True
a is c: False
interned is: True
文字列の比較は常に==を用い、s is “…”のような比較は避けてください。
is
はインターンの有無で結果が変わり、移植性と保守性を損ないます。
動的生成では==を使う(数・文字列はis禁止)
ユーザー入力、連結、フォーマット、パースなど動的に生成される値は別オブジェクトになりやすいです。
is
での比較は不安定なので==
を使います。
s1 = "".join(["ab", "c"])
s2 = "abc"
print("s1 == s2:", s1 == s2)
print("s1 is s2:", s1 is s2)
x = int("10")
y = 10
print("x == y:", x == y)
print("x is y:", x is y)
s1 == s2: True
s1 is s2: False
x == y: True
x is y: False
実装依存の最適化に頼らない
小整数のキャッシュや文字列のインターンは処理系やバージョンに依存します。
PyPyや将来のCPythonで挙動が変わる可能性があるため、等価比較は==、同一性比較だけisという原則に従うのが安全です。
使い分けガイド(チェックリストとアンチパターン)
まずは要点を早見表で整理します。
目的 | 正しい演算子 | 例 | 備考 |
---|---|---|---|
オブジェクトが同一か知りたい | is / is not | x is None | シングルトン(None, Ellipsis, NotImplemented, 自作センチネル)に限る |
値が等しいか知りたい | == / != | a == b | 文字列・数値・コンテナ・独自クラスの比較 |
大小・順序を比べたい | <, <=, >, >= | x < y | 並べ替えやしきい値判定 |
NaNか判定したい | 関数で判定 | math.isnan(x) | NaN != NaN に注意 |
値の比較は==、順序比較は<や>
値の等価は==、大小は<や>を使うのが基本です。
is
で大小や値の等価を判断してはいけません。
x, y = 10, 10.0
print("x == y:", x == y) # 値は等しいので True
print("x is y:", x is y) # 別オブジェクトなので False
print("x < 20:", x < 20) # 順序比較は < を使う
x == y: True
x is y: False
x < 20: True
同一オブジェクトかを判定するときだけis
isは「同一性」が意味を持つ場面に限定します。
代表例は以下です。
Noneチェック、センチネル検出、シングルトン(Ellipsis
、NotImplemented
)。
それ以外の通常の値比較では==
を使います。
MISSING = object()
def get_config(value=MISSING):
if value is MISSING: # 引数省略の検出(同一性)
return "using default"
return f"using {value}"
print(get_config())
print(get_config(None)) # None は明示された値として扱える
using default
using None
NaNの比較はmath.isnan()を使う
浮動小数点のNaNは特殊で、==では決して等しくならないというルールがあります。
is
も同一性を前提としない限り使えません。
math.isnan
で判定します。
import math
a = float("nan")
b = float("nan")
print("a == b:", a == b) # NaN は何とも等しくない -> False
print("a is b:", a is b) # 別オブジェクト -> False
print("math.isnan(a):", math.isnan(a))
print("math.isnan(b):", math.isnan(b))
a == b: False
a is b: False
math.isnan(a): True
math.isnan(b): True
x is 0 や s is “” はアンチパターン
x is 0やs is “”は、環境や最適化により結果が変わり得るためアンチパターンです。
== 0
や== ""
、もしくはif not s:
などの書き方を使います。
x = 0
s = "".join([]) # 動的生成で空文字
print("x is 0:", x is 0) # True になりうるが非推奨
print('s is "":', s is "") # False になりやすい(実装依存)
# 正しい比較
print("x == 0:", x == 0)
print('s == "":', s == "")
print("not s:", not s) # 空文字は偽として扱われる
x is 0: True
s is "": False
x == 0: True
s == "": True
not s: True
まとめ
isは同一性、==は同値という役割の違いを押さえると、Noneや小さな数・文字列での混乱は解消します。
実務ではNoneやセンチネルなどのシングルトンにはis、値比較には==という原則に従い、実装依存の最適化(小整数キャッシュや文字列インターン)には頼らないことが重要です。
PEP 8の方針(比較はx is None
/x is not None
、ブール値にis
を使わない)にも合致します。
これらの指針を守れば、可読性が高く、移植性と堅牢性に優れたPythonコードを書けます。