== と is の違い | Python-izm

== と is の違い

Pythonではオブジェクトを比較する方法として「==」や「is」があります。両者にはどういった違いがあるのか、また特定のオブジェクトがNoneであるかを判定する場合はどちらを用いるべきなのかを見てみましょう。

== の基本

==はオブジェクト同士が等価であるかを判定する演算子です。等価とは同じ値かどうかを指します。

test_list_1 = [100, 200, 300]
test_list_2 = [100, 200, 300]
test_list_3 = [300, 200, 100]

test_tuple_1 = (100, 200, 300)
test_str_1 = 'python-izm'


print(test_list_1 == test_list_2)
print(test_list_1 == test_list_3)
print('------------------------------')
print(test_list_1 == test_tuple_1)
print(test_str_1 == 'python-izm')
True
False
------------------------------
False
True

たとえばリストの場合test_list_1test_list_2は別のオブジェクトですが、同じ値を同じ順番で保持しているため、==はTrue(等価)となります。test_list_1test_list_3は同じ値を保持していますが、保持している順番が異なるため、==はFalse(等価ではない)になります。test_list_1test_tuple_1は同じ値、同じ順番ですが型が異なるためFalseです。文字列の比較はわかりやすいと思います。
このように一口に等価といってもリストやタプル、文字列によって等価であるかどうかの判定にはある程度の幅があります。たとえばtest_list_1test_tuple_1は前述のように同じ値、同じ順番であるため、場合によってはTrueであった方が都合が良いときもあります。これを解決する方法として、Pythonでは演算子をオーバーロードする機能が備わっていますが、少し説明が複雑になってしまうので後述します。

is の基本

オブジェクトを生成すると他のオブジェクトとは重複する事のない一意の番号を保持します。これはid関数で取得することができます。is演算子はこれを比較し、同じオブジェクトであるか(同一)を判断します。

test_list_1 = [100, 200, 300]
test_list_2 = [100, 200, 300]


print(test_list_1 == test_list_2)
print(test_list_1 is test_list_2)
print('---------------------------------------------')
print(id(test_list_1))
print(id(test_list_1) == id(test_list_1))
print(test_list_1 is test_list_1)
True
False
---------------------------------------------
44842888 (※実行環境によって異なる番号が出力されます)
True
True

上記のようにtest_list_1test_list_2==ではTrue(等価)ですが、isではFalse(同一ではない)です。これは両者が同じオブジェクトではないのでidが異なるためです。つまりid(obj_A) == id(obj_B)がTrueであるとき、obj_A is obj_Bは必ずTrueになります。

Noneの判定はどちらを使うべきか

あるオブジェクトがNoneであるかを判定する場合はis演算子を用います。その理由として以下2点を挙げることができます。

  • ==よりもisの方が処理が速い
  • ==はオーバーロードできるため、想定通りの結果とならない場合がある

まず前提として、Noneはそれ自身が何もない状態を表すオブジェクトです。次のようにNoneTypeでありidも持っています。

print(type(None))
print(id(None))
<type 'NoneType'>
506046184 (※実行環境によって異なる番号が出力されます)

次はいわゆる通常のオブジェクトを作成してみましょう。当然のことながらそのオブジェクトもidを持ちます。

val = 'python-izm'
print(id(val))
39283616 (※実行環境によって異なる番号が出力されます)

さらにそのオブジェクトへNoneを代入し、そのidを見てみましょう。Noneを代入する前後でidが変わったのを確認することができます。

val = 'python-izm'
print(id(val))

val = None
print(id(val))
39283616  (※実行環境によって異なる番号が出力されます)
506046184 (※実行環境によって異なる番号が出力されます)

文字列は更新不能なオブジェクトです。代入によってidが変わるのはそう珍しいことではありませんが、そのidNoneのものと比較してみます。

val = 'python-izm'
print(id(val))

val = None
print(id(val))

print(id(None) == id(val))
39283616  (※実行環境によって異なる番号が出力されます)
506046184 (※実行環境によって異なる番号が出力されます)
True

結果は同一なオブジェクトであると判定されました。つまりNoneが代入された時点で、すべてのオブジェクトはNoneオブジェクトとして一括りにされます。これはNoneがシングルトンであるため、常に1つしか存在しないオブジェクトだからです。そのためオブジェクトが同一であるかを見るis演算子を用いてNoneかどうかを判定するのが正しい用法です。次は何故==を用いるべきではないのかを説明します。

== のオーバーロード

まず==演算子はそのオブジェクト(クラス)が定義している__eq__メソッドの結果を見ます。__eq__が定義されていない場合は最上位の基底クラスであるobjectで定義されている__eq__メソッドの結果を利用することになりますが、これはisと同じくidを比較しています。

class TestClass:
    pass


c_1 = TestClass()
c_2 = TestClass()

print(c_1 == c_2)
print(c_1 is c_2)
False
False

つまり自ら定義したクラスなどでは独自の__eq__を定義しない限り、==isは同じ結果となります(明示的に何かしらのクラスを継承した場合は異なる可能性があります)。ただし==の方はobjectクラスの__eq__メソッドを辿って結果を返し、なおかつその処理はisと同じことを行っているだけ(同じオブジェクトであればTrue)なので、直接isで比較をした方がより速く処理を行うことができます。

次は独自の__eq__メソッドを定義してみましょう。あまりないことですが、比較対象(other)がどうであろうと必ずTrueを返すことにします。

class TestClass:

    def __eq__(self, other):
        return True


c_1 = TestClass()

print(c_1 == None)
print(c_1 is None)
True
False

結果は==でTrueとなり、isでFalseとなりました。c_1Noneではないので、想定通りの結果であるとはいえません。このようにオーバーロードで結果を操作することができるため、==Noneを判定する場合、そのクラスが__eq__メソッドを定義しているか、定義している場合どのような処理を行っているかを把握しておく必要があります。そういった手間や処理速度などを考えるとisを用いてNoneの判定を行うべきなのは明白です。