閉じる

PythonのNoneや小さな数・文字列で混乱するisと==の違い

Pythonでは同じ見た目の値でも、メモリ上のオブジェクトが同一かどうかと、値として等しいかどうかは別の概念です。

本記事ではis==の違いを軸に、Noneや小さな数値・文字列で起きがちな混乱安全な使い分けを、動作確認コードとともに丁寧に解説します。

Pythonのisと==の違い(同一性と同値)

isはオブジェクトの同一性(identity)を比較

isは「同じオブジェクトか」を判定します

アドレス(実体)が同じならTrueです。

見た目が同じ値でも、別々に作られたオブジェクトならFalseになります。

Python
# 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__を実装している場合は、その定義が使われます。

Python
# == は「値として等しいか」を見る
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になります。

Python
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 Nonex is not Noneを推奨しています。

Python
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 Trueis Falseは書かないのが実務の定石です。

単に条件式の真偽で判定します。

Python
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

シングルトン(None, Ellipsis, NotImplemented, 自作sentinel)はis

同一性で意味が決まるシングルトンはisを使います

組み込みではNoneEllipsis(...)、NotImplementedなどが該当します。

自作センチネルもisで判定します。

Python
# 組み込みシングルトンの比較
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

小さな数・文字列でisが通る理由と落とし穴

小整数のキャッシュでisが成立することがある(CPython)

CPythonは一部の小整数(一般に-5〜256)を事前にキャッシュします。

そのため、この範囲ではisがTrueになることがあります。

しかし、仕様ではなく実装依存の最適化です。

Python
# 小整数キャッシュの例 (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 0x is 1はアンチパターンです。

文字列のインターンでisが成立することがある

インタプリタは一部の文字列をインターン(共有)します。

リテラルや識別子的な文字列は同じオブジェクトを共有することがあり、isがTrueになる場合があります。

動的に生成した文字列は別オブジェクトになりやすいです。

Python
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での比較は不安定なので==を使います。

Python
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 notx is Noneシングルトン(None, Ellipsis, NotImplemented, 自作センチネル)に限る
値が等しいか知りたい== / !=a == b文字列・数値・コンテナ・独自クラスの比較
大小・順序を比べたい<, <=, >, >=x < y並べ替えやしきい値判定
NaNか判定したい関数で判定math.isnan(x)NaN != NaNに注意

値の比較は==、順序比較は<や>

値の等価は==、大小は<や>を使うのが基本です。

isで大小や値の等価を判断してはいけません。

Python
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チェックセンチネル検出シングルトン(EllipsisNotImplemented)。

それ以外の通常の値比較では==を使います。

Python
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で判定します。

Python
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 0s is “”は、環境や最適化により結果が変わり得るためアンチパターンです。

== 0== ""、もしくはif not s:などの書き方を使います。

Python
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コードを書けます。

Python 実践TIPS - コーディング効率化・Pythonic
この記事を書いた人
エーテリア編集部
エーテリア編集部

人気のPythonを初めて学ぶ方向けに、文法の基本から小さな自動化まで、実際に手を動かして理解できる記事を書いています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!