Pythonのオブジェクト比較:`is` と `==` の違い
Pythonにおけるオブジェクトの比較は、プログラミングの基本的な操作ですが、`is` と `==` という二つの演算子が存在し、その挙動の違いを理解することは、意図しないバグを防ぐ上で非常に重要です。これら二つの演算子は、一見似たような目的で使用されるように見えますが、実際には根本的に異なる比較を行っています。
`is` 演算子:同一性(Identity)の比較
`is` 演算子は、二つのオブジェクトがメモリ上の同じ位置を指しているかどうかを比較します。これは、オブジェクトの同一性(identity)をチェックしていると言えます。Pythonでは、すべてのオブジェクトはメモリ上に固有のアドレスを持っており、`id()` 関数を使用することでそのアドレスを取得できます。`is` 演算子は、この `id()` の結果を内部的に比較しているのと同じです。
`is` の挙動例
同じ値を持つ複数のオブジェクトであっても、それらが個別に生成されたものであれば、`is` 演算子では `False` と評価されることが一般的です。
a = [1, 2, 3] b = [1, 2, 3] c = a print(a is b) # False (異なるリストオブジェクト) print(a is c) # True (同じリストオブジェクトを指している)
この例では、`a` と `b` は同じ値を持つリストですが、それぞれ独立して生成されたため、メモリ上の異なる場所に存在します。そのため `a is b` は `False` になります。一方、`c` は `a` を代入されたため、`a` と全く同じリストオブジェクトを指しています。したがって `a is c` は `True` になります。
`is` が `True` になるケース
* 同じミュータブルオブジェクト(リスト、辞書など)を代入した場合:上記の例の `a is c` がこれに該当します。
* イミュータブルオブジェクト(整数、文字列、タプルなど)で、Pythonの内部的な最適化(インターニング)が適用された場合:Pythonは、特定の範囲の整数(通常は-5から256)や短い文字列など、頻繁に使用されるイミュータブルオブジェクトについては、メモリの節約のために単一のインスタンスを使い回すことがあります。このような場合、同じ値を持つオブジェクトでも `is` が `True` を返すことがあります。
x = 10 y = 10 print(x is y) # True (インターニングされている可能性が高い) s1 = "hello" s2 = "hello" print(s1 is s2) # True (インターニングされている可能性が高い)
しかし、このインターニングは実装依存であり、常に保証されるものではないことに注意が必要です。特に、動的に生成されたり、長かったりする文字列などでは、インターニングされない場合もあります。
`==` 演算子:等価性(Equality)の比較
`==` 演算子は、二つのオブジェクトの値が等しいかどうかを比較します。これは、オブジェクトの等価性(equality)をチェックしていると言えます。`==` 演算子の挙動は、比較されるオブジェクトの型によって異なります。各型は、自身の `__eq__()` メソッドを定義しており、`==` 演算子はこのメソッドを呼び出して比較を行います。
`==` の挙動例
同じ値を持つ異なるオブジェクトであっても、`==` 演算子では `True` と評価されることが一般的です。
a = [1, 2, 3] b = [1, 2, 3] c = a print(a == b) # True (リストの値が等しい) print(a == c) # True (リストの値が等しい)
この例では、`a` と `b` は値が同じであるため `a == b` は `True` になります。`c` は `a` と同じオブジェクトを指しているため、値も当然同じなので `a == c` も `True` になります。
`==` が `True` になるケース
* 数値、文字列、リスト、タプル、辞書など、その型で定義された比較ロジックに基づいて値が等しい場合。
* カスタムクラスで `__eq__()` メソッドが適切に実装されている場合:クラスのインスタンス間で値の等価性を定義したい場合、`__eq__()` メソッドをオーバーライドする必要があります。
class MyNumber:
def __init__(self, value):
self.value = value
def __eq__(self, other):
if isinstance(other, MyNumber):
return self.value == other.value
return False
num1 = MyNumber(10)
num2 = MyNumber(10)
num3 = MyNumber(20)
print(num1 == num2) # True (valueが等しい)
print(num1 == num3) # False (valueが異なる)
`is` と `==` の使い分け
* オブジェクトの参照(メモリ上の同一性)を厳密に確認したい場合:`is` を使用します。例えば、`None` との比較では `is` を使うのが一般的です。`None` はシングルトンオブジェクトであり、常に同じインスタンスが使用されるため、`is None` は `== None` よりも高速で、意図も明確になります。
my_var = None
if my_var is None:
print("変数にNoneが代入されています。")
* オブジェクトの内容(値)が等しいかどうかを確認したい場合:`==` を使用します。ほとんどの場合、オブジェクトの比較には `==` が適しています。
### 比較のまとめ
| 演算子 | 比較対象 | 比較内容 |
| :—– | :———– | :——————- |
| `is` | オブジェクト | メモリ上の同一性 |
| `==` | オブジェクト | 値の等価性(内容) |
### 注意点と補足
* ミュータブルオブジェクト(リスト、辞書など)の場合、`is` と `==` の挙動はしばしば異なります。異なるリストオブジェクトでも、値が同じであれば `==` は `True` を返しますが、`is` は `False` を返します。
* イミュータブルオブジェクト(整数、文字列、タプルなど)の場合、Pythonのインターニングにより、同じ値を持つオブジェクトが同じメモリ領域を共有することがあります。そのため、`is` が `True` を返すケースも多く見られますが、これは保証された挙動ではありません。したがって、イミュータブルオブジェクトであっても、値の比較をしたい場合は `==` を使用するのが安全で、意図も明確になります。
* カスタムクラスでは、`==` 演算子の挙動を `__eq__()` メソッドで定義しない限り、デフォルトでは `is` と同じようにオブジェクトの同一性を比較します。そのため、カスタムクラスで値の比較を行いたい場合は、必ず `__eq__()` メソッドを実装する必要があります。
Pythonでのオブジェクト比較は、`is` と `==` の違いを正しく理解し、状況に応じて適切な演算子を選択することが、コードの可読性と正確性を高める上で不可欠です。
