Pythonではis
と==
がしばしば混同されます。
特にNone
の比較や、小さな整数や短い文字列でis
がたまたまTrue
になる挙動が混乱の元です。
本記事では、両者の違いを「同一性」と「等価性」の観点から丁寧に解説し、PEP 8に沿った安全な書き方や、実装依存の落とし穴を避けるための具体的なコツを示します。
isと==の違いは同一性と等価性
isは同一オブジェクトかを判定
is
は2つの変数が「まったく同じオブジェクト(同じ参照先)」であるかどうかを判定します。
is
は演算子であり、ユーザー定義のクラスで振る舞いを上書きできません。
内部的にはid()
が同じかを調べるイメージです。
サンプルコード
# is は「同一性(identity)」の比較
a = [1, 2, 3]
b = [1, 2, 3]
c = a # c は a と同じリストオブジェクトを参照
print(a is b) # 別々に作られたリストなので False
print(a is c) # 同じオブジェクトを指すので True
# 参照先(オブジェクトID)を見てみる
print(id(a), id(b), id(c))
False
True
140123456789456 140123456789888 140123456789456
==は値が等しいかを判定
==
は「等価性(value equality)」の比較です。
組み込み型やクラスの__eq__
メソッドが呼び出され、値が等しいかどうかが判定されます。
つまり、別オブジェクトであっても中身が同じならTrue
になり得ます。
サンプルコード
# == は「値の等価性」の比較
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b) # 要素が同じなので True
print(a is b) # オブジェクトは別なので False
True
False
迷わないための基本ルール
日常のコーディングでは、次の原則に沿えばまず迷いません。
- 値が同じかを調べたいときは
==
を使います。 None
のようなシングルトン(唯一のインスタンスが存在する特別な値)との比較にはis
/is not
を使います。
この違いは次の表に要点をまとめます。
演算子 | 意味 | 拡張可否 | 典型用途 |
---|---|---|---|
is | 同一性(同じオブジェクトか) | 上書き不可 | None やTrue などシングルトン判定、キャッシュ共有の確認 |
== | 等価性(値が等しいか) | __eq__ で上書き可能 | 数値・文字列・コレクションの値比較、ドメインオブジェクトの等価性 |
Noneの比較はisを使う(PEP 8)
Noneに==を使わない理由
PEP 8では、None
との比較はis
/is not
を使うことが推奨されています。
None
はシングルトンであり、同一性で判定するのが正確です。
また、==
はユーザー定義の__eq__
に委ねられるため、思わぬ振る舞いを引き起こす可能性があります。
サンプルコード
# None 比較は is / is not を使う
from typing import Optional
def find_even_or_none(x: int) -> Optional[int]:
# 偶数なら値を返し、奇数なら None を返す
return x if x % 2 == 0 else None
result = find_even_or_none(3)
# 良い例
if result is None:
print("値は見つかりませんでした")
# 悪い例 (PEP 8 に反する)
if result == None: # noqa: E711 (サンプルとして記載)
print("この比較方法は推奨されません")
値は見つかりませんでした
この比較方法は推奨されません
is not Noneの安全な書き方
「値が存在するなら処理する」という場面ではis not None
を使います。
0
や空文字""
は偽と判定されますが、None
とは意味が異なるため明確に区別するのが安全です。
サンプルコード
value = 0
# None との厳密な区別が必要なとき
if value is not None:
print("値は None ではありません") # 0 でもここに入る
値は None ではありません
TrueやFalseとの比較で避ける書き方
if x == True
やif x == False
のような比較は避けます。
真偽値の判定は「Truthiness(真値性)」に従ってif x:
やif not x:
と書くのがPythonicです。
どうしてもTrue
そのものを厳密に判定したい特別な理由がある場合のみx is True
を検討します。
サンプルコード
# Truthiness を使った書き方が推奨
flag = 1 # 1 は True に等価だが、True そのものではない
# 推奨
if flag:
print("真と判定されました")
# 非推奨
if flag == True: # noqa: E712
print("== True は避けましょう")
# 厳密に True そのものかを見たい稀なケースのみ
flag2 = True
if flag2 is True:
print("厳密に True 本体です")
真と判定されました
== True は避けましょう
厳密に True 本体です
小さな数や短い文字列でisがTrueになる理由
整数のキャッシュは実装依存である
CPythonではよく使われる小さな整数(典型的には-5〜256)がキャッシュされます。
そのため、この範囲の整数は同一オブジェクトを共有しやすく、is
がTrue
になることがあります。
しかし範囲や挙動は実装依存であり、バージョンや処理系で変わる可能性があるため、これに依存したコードを書いてはいけません。
サンプルコード
# 小さな整数は同一オブジェクトを共有することがある
a = 256
b = 256
print(a is b) # 多くの CPython で True になりやすい
# 大きめの整数は共有されないことが多い(実装依存)
x = int("257") # 実行時に生成して定数畳み込みを避ける
y = int("257")
print(x == y) # 値は等しい
print(x is y) # 多くの CPython で False になりやすい
True
True
False
文字列のインターニングと偶然の一致
短い文字列や識別子風の文字列は、Pythonが自動的にインターニング(同じオブジェクトを共有)することがあります。
また、同一モジュール内の同一リテラルはコンパイル時にまとめられる場合があります。
これも実装依存で、is
がTrue
になるのは「たまたま」です。
値比較には必ず==
を使います。
必要に応じてsys.intern
で明示的にインターンできます。
サンプルコード
import sys
s1 = "python"
s2 = "python"
print(s1 is s2) # True になりやすいが保証はない
t1 = "".join(["py", "thon"]) # 実行時に生成
t2 = "".join(["py", "thon"])
print(t1 == t2) # 値は等しい
print(t1 is t2) # 別オブジェクトになりやすい
u1 = sys.intern("data-key")
u2 = sys.intern("data-" + "key") # インターンすれば同一化される
print(u1 is u2) # True
True
True
False
True
id()で参照先を確認する
デバッグ時に「同じオブジェクトか」を確かめたい場合はid()
で参照先を確認できます。
ただし、id()
値の具体的な数値に意味を持たせるべきではありません。
サンプルコード
a = "abc"
b = "".join(["a", "b", "c"])
print(id(a), id(b)) # たいていは異なる
print(a == b, a is b) # 等価性は True、同一性は False
140123456700000 140123456700512
True False
正しい使い分けとアンチパターン
値の比較は==、シングルトン(Noneなど)はis
日常の比較は==
で十分です。
None
やTrue
、False
、Ellipsis
(...
)などのシングルトンを判定するときにのみis
/is not
を使います。
これにより、実装依存のキャッシュやインターニングに影響されない堅牢なコードになります。
サンプルコード
data = {"count": 0}
# 値の比較
if data["count"] == 0:
print("カウントは 0 です")
maybe_none = None
# シングルトン判定
if maybe_none is None:
print("None です")
カウントは 0 です
None です
リストや文字列の比較にisを使わない
ミュータブル(可変)なリストや、イミュータブルな文字列であっても、is
で比較してはいけません。
is
は「同じオブジェクトか」を見るだけで、「同じ内容か」は判定しません。
内容の比較には常に==
を使います。
サンプルコード
# 文字列でのアンチパターン
s = "abc"
t = "".join(["a", "b", "c"])
print(s == t) # 内容は同じ -> True
print(s is t) # オブジェクトは別 -> False (is を使うべきでない)
# リストでも同様
a = [1, 2]
b = [1, 2]
print(a == b) # True
print(a is b) # False
True
False
True
False
カスタムクラスの==実装(eq)での注意点
独自クラスで値の等価性を定義するなら__eq__
を正しく実装します。
型が異なる場合はNotImplemented
を返し、ハッシュ可能にしたい場合は__hash__
との整合性にも注意します。
is
は同一性のみを判定し、__eq__
には影響しません。
良い実装例(dataclassを活用)
from dataclasses import dataclass
@dataclass(frozen=True) # 不変にしてハッシュと等価性の整合を取りやすく
class Point:
x: int
y: int
p1 = Point(1, 2)
p2 = Point(1, 2)
print(p1 == p2) # 値が同じ -> True
print(p1 is p2) # 別インスタンス -> False
True
False
手動で__eq__を実装する場合
class Point:
def __init__(self, x: int, y: int) -> None:
self.x = x
self.y = y
def __eq__(self, other):
# 型が違う場合は NotImplemented を返すのが礼儀
if not isinstance(other, Point):
return NotImplemented
return (self.x, self.y) == (other.x, other.y)
p1 = Point(1, 2)
p2 = Point(1, 2)
p3 = p1
print(p1 == p2) # True (値が等しい)
print(p1 is p2) # False (同一ではない)
print(p1 is p3) # True (同一)
True
False
True
なお、__eq__
を上書きして可変にしたクラスを辞書キーや集合に入れる場合は__hash__
の設計が必要です。
等価なオブジェクトは同じハッシュであるべきで、変更不能(frozen)にするのが安全です。
NumPyの配列のように、==
が要素ごとの配列を返す型もあるため、ライブラリごとの等価性の定義にも注意します。
まとめ
is
と==
の違いは、「同一性」と「等価性」という根本から理解すると迷いません。
is
は同一オブジェクトかを判定し、特にNone
などのシングルトンで使います。
==
は値の等価性を判定し、通常の数値、文字列、コレクション、そしてカスタムクラスの比較に用います。
小さな整数のキャッシュや文字列のインターニングでis
が偶然True
になることはありますが、これは実装依存であり、設計の拠り所にしてはいけません。
PEP 8に従い、None
比較はis
、真偽判定はTruthiness、値比較は==
という原則を守れば、初学者でも堅牢で読みやすいPythonicなコードを書けます。