Pythonのisと== は何が違う? Pythonの比較演算子で知っておくべき罠
Pythonを書き始めて少し経つと、if文などで値を比較する機会が増えてきます。
その際、なんとなく「同じかどうか」を調べるために == や is を使っていませんか。
実は、この2つは似ているようで全く別物であり、使い分けないと思わぬバグを生む原因になります。 私もエンジニア10年のキャリアの中で、この違いを混同したことによるトラブルを何度も目撃してきました。
今回は、初心者が必ずと言っていいほどハマる「比較演算子の罠」について、徹底的に解説します。 この記事を読めば、自信を持って正しい比較ができるようになるはずです。
結論:値が同じか、それとも「同一物」か¶
最初に、最も大切な結論からお伝えします。
== は値(中身)が同じかどうかを判定し、is はオブジェクト(実体)が同じかどうかを判定します。
言葉だけではイメージしにくいので、よく例えられる「双子」の話で考えてみましょう。 見た目も性格も名前も全く同じ双子がいたとして、彼らは「別人(別の実体)」ですよね。
この場合、== で比較すると「同じ」になりますが、is で比較すると「別人」なので「違う」という結果になります。
一方で、一人の人間をニックネームで呼んでいる場合は、中身も実体も同じなのでどちらも「同じ」になります。
【関連記事】 Pythonの真偽値を深く知る!if文で意外とハマるNoneや空リストの挙動を解説
メモリ上の住所で考える¶
コンピューターの世界では、全てのデータ(オブジェクト)はメモリ上のどこかに保存されています。 この保存場所のことをメモリ感地(アドレス)と呼びます。
is 演算子がチェックしているのは、まさにこの「住所」です。
二つの変数が、メモリ上の全く同じ場所を指しているときだけ、is は真を返します。
== 演算子の正体:値の等価性¶
まずは、私たちが日常的に使う == 演算子について深掘りしていきましょう。
これは専門用語で等価性(Equality)を判定すると言います。
Pythonの数値や文字列、リストの中身が「人間から見て同じに見えるか」をチェックするための道具です。 例えば、以下のコードを見てみましょう。
# リストの比較
list_a = [1, 2, 3]
list_b = [1, 2, 3]
print(list_a == list_b)
# 出力: True
この例では、list_a と list_b は別々に作成されたリストですが、中身はどちらも [1, 2, 3] です。
そのため、== で比較すると「値が等しい」と判断され、True が返ってきます。
__eq__ メソッドの働き¶
なぜ Python は中身を比較できるのかというと、それぞれのオブジェクトが「自分の値はこうやって比較してね」というルールを持っているからです。
これを内部的には __eq__ という特別なメソッドで定義しています。
もしあなたが自分でクラスを作った場合、このルールを定義しないと == が意図通りに動かないことがあります。
標準的なデータ型(int, str, listなど)は、最初からこのルールが親切に設定されているのです。
is 演算子の正体:オブジェクトの同一性¶
次に、トラブルの元になりやすい is 演算子について詳しく見ていきましょう。
こちらは同一性(Identity)を判定するための演算子です。
中身がどれだけ似ていても、それがメモリ上で「別の箱」に入っているなら、is は容赦なく False を返します。
先ほどのリストの例を、今度は is で比較してみます。
list_a = [1, 2, 3]
list_b = [1, 2, 3]
print(list_a is list_b)
# 出力: False
結果が変わりましたね。
中身は同じでも、list_a と list_b はメモリ上の異なる場所に確保された「別々のインスタンス」だからです。
id() 関数で住所を確認する¶
自分が今扱っている変数がどこにあるのかを確認するには、id() という関数を使います。
この関数は、オブジェクト固有の識別番号(住所)を返してくれます。
print(id(list_a)) # 例: 1405234857
print(id(list_b)) # 例: 1405234892
is 演算子は、内部的には id(a) == id(b) を実行しているのとほぼ同じことをしています。
住所が完全に一致する場合のみ、本当の意味で「同じもの」だと認めるのです。
Pythonの魔法:なぜか is で True になる罠¶
ここからが、初心者を混乱させる Python の面白い(そして恐ろしい)仕様の話です。 「中身が同じなら別々の場所にあるはず」という原則が、特定の条件下で崩れることがあります。
例えば、数値の比較で不思議な現象が起こります。 以下のコードの結果を予想してみてください。
a = 100
b = 100
print(a is b) # 結果はどうなる?
多くの人は、別々の変数なのだから False だと思うでしょう。
しかし、実際には True が出力されます。
小さな整数のキャッシュ(Interning)¶
Python(特に標準的なCPython)には、よく使う小さな整数をあらかじめメモリ上に用意しておく仕組みがあります。 具体的には -5 から 256 まで の数値は、どこで使っても同じメモリ住所を指すように最適化されています。
これを「整数のキャッシュ(Interning)」と呼びます。
同じ 100 という数値を何度も別々の場所に作るのはメモリの無駄なので、一つを使い回しているのです。
数値が大きくなると挙動が変わる¶
では、この範囲を超えた数値ではどうなるでしょうか。 257 以上の数値で試してみると、驚きの結果になります。
x = 300
y = 300
print(x is y)
# 出力: False (環境によって True になることもありますが、基本は False です)
このように、数値の大きさによって is の結果が変わってしまうのです。
これが「数値を is で比較してはいけない」と言われる最大の理由です。
文字列でも起こる「インターン化」の謎¶
数値だけでなく、文字列でも同様の最適化が行われることがあります。 短い英単語などは、Pythonが自動的に「これ、さっきも使ったから同じ場所にまとめちゃおう」と判断することがあります。
しかし、この判断基準は非常に複雑で、実行環境や文字列の作り方(結合など)によって変わります。
そのため「昨日までは is で動いていたのに、今日は動かない」という怪現象が起こるのです。
【関連記事】 100行のコードが10行に?Pythonic(パイソンらしい)な書き方への第一歩
None の比較だけは is を使うのが正解¶
ここまで「is は危険だ」という話をしてきましたが、たった一つだけ、絶対に is を使うべき場面があります。
それが None との比較 です。
Pythonにおいて None は、システム全体でただ一つの実体しかないことが保証されている特別な値(シングルトン)です。
そのため、値の比較をする == よりも、実体を確認する is の方が推奨されています。
value = get_data()
# 推奨される書き方
if value is None:
print("データがありません")
# 非推奨の書き方
if value == None:
print("これでも動くけれど、お作法としては良くない")
なぜ is None なのかというと、== を使うと「誰かが __eq__ を書き換えて None と等しいと嘘をついている」場合に誤判定する可能性があるからです。
is を使えば、偽物に騙されることなく本物の None かどうかを判定できます。
比較演算子の違いを一覧表で整理¶
ここで一度、知識を整理するために比較表を見てみましょう。 何を確認したいのかによって、使う道具を選ぶ必要があります。
| 演算子 | 比較の種類 | 判定の基準 | 主な利用シーン |
|---|---|---|---|
== |
等価性 (Equality) | オブジェクトの「値」が同じか | ほとんどの比較(数値、文字列、リストなど) |
is |
同一性 (Identity) | オブジェクトの「メモリ上の住所」が同じか | None, True, False との比較 |
!= |
不等価 | 値が異なるか | == の逆の結果が欲しいとき |
is not |
非同一 | 実体が異なるか | is None の否定として is not None で使う |
現場で遭遇した恐ろしいバグの話¶
エンジニア歴10年の私の経験から、一つ具体的な失敗談をお話しします。 あるプロジェクトで、設定ファイルのフラグをチェックするコードがありました。
そのフラグには True や False が入る予定でしたが、ある時プログラムがうまく動きませんでした。
原因を探ってみると、以下のような比較が書かれていたのです。
# 実際にあった危険なコード
if flag is True:
do_something()
一見問題なさそうですが、後に他の開発者が、フラグに「1(数値)」を入れるように変更してしまいました。
Pythonでは 1 == True は成り立ちますが、1 is True は成り立ちません。
結果として、フラグが立っているはずなのに do_something() が実行されないというサイレントなバグが発生しました。
もしここが if flag: や if flag == True: であれば、問題は起きなかったでしょう。
迷った時のガイドライン¶
最後に、明日から迷わずに済むためのシンプルなルールを共有します。 基本的には「何が何でもこれに従う」という強い気持ちでOKです。
- 値を比べたいなら、常に
==を使う。 Noneを確かめたい時だけ、is Noneを使う。is Trueやis Falseは原則使わない。
これだけで、あなたのコードの安全性は劇的に向上します。
「もしかして is の方が速いかも?」という誘惑に負けてはいけません。
確かに is は住所を見るだけなので高速ですが、そのわずかな差のためにプログラムの正確性を犠牲にする価値はありません。
パフォーマンスよりも、まずは「誰が読んでも正しく動くこと」を優先しましょう。
【関連記事】 Pythonの禅とは?「import this」に隠された秘密を徹底解説
まとめ:正しく比較して Pythonic なエンジニアへ¶
Pythonの is と == の違い、そして背後にあるメモリの仕組みについて理解できましたでしょうか。
この使い分けができるようになると、Pythonが裏側でどう動いているのかが少しずつ見えてくるはずです。
「同じ」という言葉には、「中身が同じ」と「そのものズバリ同じ」の二つの意味があること。 そして、Pythonはその違いを厳格に区別していることを忘れないでください。
初心者のうちは is を封印して、is None だけを使うようにしても良いくらいです。
地味な知識かもしれませんが、こうした基礎の積み重ねが、将来的に複雑なシステムを支える力になります。
もし周りで is を乱用しているコードを見かけたら、ぜひこの記事の話を思い出して教えてあげてください。
あなたの Python 学習が、より確かなものになることを応援しています。
ここまでお読みいただきありがとうございました。