Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】Understanding the difference between identity and equality, and why it matters more than it seems.

2025年09月20日に「Dev.to」が公開したITニュース「Understanding the difference between identity and equality, and why it matters more than it seems.」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

Pythonの`==`は値の等価性を、`is`はオブジェクトの同一性を比較する。内容が同じでも`is`は異なる。`None`など特定オブジェクト確認に`is`を用いるため、違いを理解し使い分けが重要だ。

ITニュース解説

Pythonの学習を進める中で、一見すると同じような働きをするように見えるものの、実際には非常に異なる意味を持つ概念に出会うことは少なくない。その典型的な例が、値の比較に使う==(同等演算子)と、オブジェクトの同一性を確認するis(同一性演算子)である。これらはどちらも「二つのものが同じであるか?」という問いに答えるように見えるが、実はそれぞれが問うている内容は全く異なる。この違いを理解することは、システムエンジニアとして堅牢で効率的なコードを書く上で非常に重要となる。

まず、この二つの演算子がどのような質問に答えているのかを明確にする。==演算子は、「これら二つの値は同じ内容を持っているか?」という質問に答える。これは、変数が保持するデータの中身が同じかどうかを判断する。例えば、二つのリストが全く同じ要素を同じ順序で持っている場合、それらは==によって同等であると判断される。一方、is演算子は、「これら二つの値は、メモリ上で同じオブジェクトを参照しているか?」という質問に答える。これは、変数が指し示している実体、つまりメモリ上に存在する特定のデータ構造が同じであるかどうかを判断する。

具体的な例で考えてみよう。 a = [1, 2, 3] b = [1, 2, 3] という二つのリスト変数があるとする。この場合、print(a == b)の結果はTrueとなる。なぜなら、両者のリストの中身はどちらも[1, 2, 3]であり、内容が全く同じだからだ。しかし、print(a is b)の結果はFalseとなる。これは、abがそれぞれメモリ上に別々のリストオブジェクトを作成し、それを参照しているためだ。たとえ中身が同じであっても、それらは物理的に別々の存在として扱われる。一方のリストに変更を加えても、もう一方のリストの内容は変わらないのと似ている。このメモリ上の別々の実体こそが、同等性と同一性の核心的な違いである。

では、なぜis演算子が必要なのだろうか。常に==を使えば良いのではないかと思うかもしれない。isは内容の同等性ではなく、オブジェクトそのものの同一性に関心がある場合に非常に役立つ。特に、Noneのような「番人オブジェクト(sentinel object)」をチェックする際に非常に重要となる。Noneは「値がない」ことを示す特別なオブジェクトであり、Pythonの設計上、システム全体で唯一の存在であることが保証されている。 例えば、ユーザーからの入力がなかったかどうかを確認するコードで、 if user_input is None: という記述を見ることがあるだろう。このコードは「user_inputの内容がNoneと"似ている"か」を問うのではなく、「user_inputがまさにNoneという特別なオブジェクトそのものであるか」を問うている。これにより、コードの意図が明確になり、予期せぬ挙動を防ぐことができる。Noneのような特定のシングルトンオブジェクト(システム内で一つしか存在しないと定められたオブジェクト)を確認する際には、isを使うのが慣例であり、最も正確で推奨される方法である。

次に、不変型(Immutable Types)におけるisの挙動の特殊性について解説する。不変型とは、一度作成されると内容を変更できないデータ型のことだ。整数(int)、文字列(str)、タプル(tuple)などがこれに該当する。ここで少し戸惑うような挙動を見ることがある。 例えば、 a = 1000 b = 1000 とした場合、print(a == b)Trueになるが、print(a is b)はほとんどのシステムでFalseになるだろう。しかし、 x = 5 y = 5 とした場合、print(x == y)Trueになり、print(x is y)Trueになることがある。 なぜこのような一貫性のない挙動が起きるのだろうか。これは、Pythonの最も一般的な実行環境であるCPython(C言語で実装されたPythonインタープリタ)の内部的な最適化、つまり「実装詳細」に起因する。CPythonは、プログラムの性能を向上させるために、-5から256までの小さな整数や、短い特定の文字列など、頻繁に使われる不変の値をメモリ上で事前にキャッシュし、再利用することがある。この最適化により、同じ値を持つ変数が実際にメモリ上の同じオブジェクトを参照することが起こりうるのだ。しかし、これはPythonの言語仕様によって保証された挙動ではなく、あくまでCPythonの実装上の都合によるものであるため、この挙動に依存してプログラムを組むべきではない。このような実装詳細は、Pythonのバージョンや実行環境によって変わる可能性があるため、注意が必要だ。

さらに、リストやタプルのようなコンテナ型について考えてみよう。 空のリストの場合、 print([] == [])Trueとなる。どちらも空という内容だからだ。 しかし、print([] is [])Falseとなる。Pythonは[]と書かれるたびに、メモリ上に新しい空のリストオブジェクトを作成する。これらは内容が同じでも、別々のオブジェクトなのでFalseとなる。 では、空のタプル()の場合はどうだろうか。 print(() == ())Trueとなる。 そして、print(() is ())Trueになることが多い。これは空のタプルも不変型であり、CPythonが性能最適化のために単一のオブジェクトをキャッシュして再利用しているためだ。しかし、これも不変整数と同様にCPythonの実装詳細であり、Pythonの言語仕様で保証されているわけではない。実際、Python 3.8以降では、() is ()のような比較に対して構文警告(SyntaxWarning)を出すことで、プログラマーにこの挙動に依存しないよう促している。

これらの違いを踏まえた上で、ベストプラクティスを理解しておくことは非常に重要だ。 まず、二つの値の「内容」が同じであるかどうかを比較する際には、常に==演算子を使用する。ほとんどの場合、私たちは変数そのものが持つ意味や内容が同じであるかに関心があるため、これが適切な選択となる。 次に、オブジェクトの「同一性」をチェックする場合には、is演算子を使用する。これは、NoneTrueFalseといったPythonのシングルトンオブジェクトや、プログラム内で特別に定義した「番人オブジェクト」が、まさにその特定のオブジェクトであるかを確認する場合に適用される。このような特定のオブジェクトであるかどうかの確認には、isを使うことで意図が明確になり、バグのリスクを減らせる。 一方で、数値リテラルや文字列リテラル、あるいは他の不変の値に対してisを使うことは、極力避けるべきである。先述のCPythonの最適化のような「たまたま同じオブジェクトになった」という状況は、将来的に変わる可能性があるため、信頼できる挙動ではない。もし特殊な理由があって不変値の同一性をチェックする必要がある場合は、その理由をコードに明確に記述し、他の開発者が理解できるようにすることが重要だ。

結論として、==isは、Pythonにおける「同等性」と「同一性」という、根本的に異なる概念を表している。==は「表面的な見た目」や「内容」が同じであるかを問うのに対し、isは「本質的な存在」、つまりメモリ上のオブジェクトが同じであるかを問う。この違いを正確に理解することは、単なる技術的な詳細にとどまらない。それはプログラムがどのように動作し、メモリをどのように管理しているかという深い洞察を与え、より堅牢で予測可能な、そして効率的なコードを書く上で不可欠なスキルとなる。システムエンジニアを目指す上で、この二つの演算子の違いと、それがなぜ重要なのかを深く理解しておくことは、間違いなくあなたのプログラミング能力を一段階引き上げるだろう。

関連コンテンツ

関連IT用語