Python の is と == の動作と違い


is
オブジェクトが同じであるかどうかを判定する比較演算子
==
オブジェクトの値が等しいかどうかを判定する比較演算子
正確には値が同じであるかどうかを確認するように実装されて、
あるいはしなければならず、その実装はクラスごとに異なります。

4.3. 比較 < 4. 組み込み型


is も == も、ともに比較演算子です。

comp_operator ::=  "<" | ">" | "==" | ">=" | "<=" | "!="
                   | "is" ["not"] | ["not"] "in"

6.10. Comparisons

1. 比較演算子 is

2 つの変数に代入されたオブジェクトが同じオブジェクトであれば True を返します。

言い換えると 2 つの変数に代入されたオブジェクトの identity, 同一性が等しければ True を返します。

オブジェクトの同一性, identity とはオブジェクトに1つ1つ割り当てられた背番号のようなもので id 関数で調べることができます。
Python の代入について

>>> a = [1, 2, 3]
>>> id(a)
4350378504
>>>
>>> b = [1, 2, 3]
>>> id(b)
4350382728
>>>
>>> c = a
>>> id(c)
4350378504
>>> 
>>>
>>> # 変数 a と 変数 b に代入されているのは、別のオブジェクト
>>> a is b
False
>>>
>>> # 変数 a と 変数 c に代入されているのは、同じオブジェクト
>>> a is c 
True
>>>


is 比較演算子は、次の式と等価です。

a is b
id(a) == id(b)


6.10.3. 同一性の比較
is と is not 演算子はオブジェクトの同一性 identity について試験します: x と y が同じオブジェクトである場合のみ、x is ytrue を返します。オブジェクトの同一性 identity は id() 関数を使って確認できます。x is not y は、反対の真偽値を返します。[4]
The operators is and is not test for object identity: x is y is true if and only if x and y are the same object. Object identity is determined using the id() function. x is not y yields the inverse truth value.[4]

2. 比較演算子 ==

簡単に言えば == は、値が等しいかどうかを確認しています。

正確に言えば == は、値が等しいかどうかを確認するように実装されていて、その実装のされ方はクラスごとに異なります。



4.3. 比較
演算 意味
== 等しい

◯ 値

Pythonと言うと、オブジェクトの属性に代入されたオブジェクト、または list, tuple, str などのシーケンスに代入されたオブジェクトを指すことが多いです。

>>> # オブジェクトの値
>>> class C: pass
... 
>>> obj = C()
>>> obj.a = 100
>>> obj.a
100
>>>
>>> # シーケンスオブジェクトの値
>>> s = "Hello, world!"
>>> s[0]
'H'

Python で mutable と immutable の違い

◯ 簡単に言えば

値が等しいかどうかを確認しています。

>>> a = [1, 2, 3]
>>> b = [1, 2, 3]
>>>
>>> # a と b の値は同じ
>>> a == b
True
>>> 
>>> # a に 4 を加えると同じ値ではなくなる
>>> a.append(4)
>>> a == b
False
>>> 

◯ 正確には言えば

値が等しいかどうかを確認するように実装されていて、その実装のされ方はクラスごとに異なります。

つまり == は値が同じであるかどうかを判定しているわけではありません。値が同じでも False が返される例を見てみましょう。

>>> class C: pass
... 
>>> 
>>> 
>>> obj1 = C()
>>> obj1.a = 100
>>> 
>>> obj2 = C()
>>> obj2.a = 100
>>>
>>> # 値が同じでも False が返されています。 
>>> # そもそもこいつは一体何を比較しているのでしょうか?
>>> obj1 == obj2
False


では == はどのように実装されているのでしょうか。このあと具体例を3つ見ていきたいと思います。

実装例 1. ユーザが定義したクラス

実はユーザが定義したクラスは __eq__ メソッドを定義することで == 演算子を実装することができます。反対に is 演算子はできません。こういうのをオペレータオーバーロード, operator overload なんて表現されたりもします。

"""矩形の等号 == を表現する書き方について考える."""


def sample_code():
    print(Rectangle(1, 1, 2, 2) == Rectangle(1, 1, 2, 2))
    print(eq(Rectangle(1, 1, 2, 2), Rectangle(1, 1, 2, 2)))


# こんな矩形のクラス
class Rectangle(object):
    def __init__(self, x1, y1, x2, y2):
        self.x1 = x1
        self.y1 = y1
        self.x2 = x2
        self.y2 = y2

    # id が等しいかよりも
    # 値が等しいかの方が理解しやすいコードになる。
    # だから正確には違うけど
    # == は値が等しいかどうかの判定と書きました。
    def __eq__(self, other):
        return \
            self.x1 == other.x1 and \
            self.y1 == other.y1 and \
            self.x2 == other.x2 and \
            self.y2 == other.y2


def eq(rectangle_a, rectangle_b):
    return \
        rectangle_a.x1 == rectangle_b.x1 and \
        rectangle_a.y1 == rectangle_b.y1 and \
        rectangle_a.x2 == rectangle_b.x2 and \
        rectangle_a.y2 == rectangle_b.y2


if __name__ == '__main__':
    sample_code()


その他にも実装できる比較演算子がありますが

3.1. オブジェクト、値、および型

object.__lt__(self, other)
object.__le__(self, other)
object.__eq__(self, other)
object.__ne__(self, other)
object.__gt__(self, other)
object.__ge__(self, other)

これらはいわゆる "拡張比較 (rich comparison)" メソッドです。
演算子シンボルとメソッド名の対応は以下の通りです:
x < y は x.__lt__(y) を呼び出します;
x <= y は x.__le__(y) を呼び出します;
x==y は x.__eq__(y) を呼び出します;
x!=y は x.__ne__(y) を呼び出します;
x > y は x.__gt__(y) を呼び出します;
x>=y は x.__ge__(y) を呼び出します。


is はできません。

4.3. 比較
is および is not 演算子の振る舞いはカスタマイズできません。

実装例 2. object クラス

組込型である object クラスに適用される == 演算子の動作は is と同じで、同じオブジェクトであるかどうかを比較しているだけです。

>>> # なんと object クラスのインスタンスにも
>>> # == 演算子が使える。
>>> # 中身は identity を比較しているだけ。
>>> object() == object() 
False
>>> 
>>> # identity が等しいならば true を返す。
>>> obj = object()
>>> obj === obj
True
>>>

等価性比較 (== および !=) のデフォルトの振る舞いは、オブジェクトの同一性に基づいています。 従って、同一のインスタンスの等価性比較の結果は等しいとなり、同一でないインスタンスの等価性比較の結果は等しくないとなります。 デフォルトの振る舞いをこのようにしたのは、全てのオブジェクトを反射的 (reflexive つまり x is y ならば x == y) なものにしたかったからです。
6.10.1. 値の比較


ここで上の方で紹介した値が同じでも False が返される例についても動作が理解できます。

>>> class C: pass
... 
>>> 
>>> 
>>> obj1 = C()
>>> obj1.a = 100
>>> 
>>> obj2 = C()
>>> obj2.a = 100
>>> 
>>> # object クラスの == の処理が呼び出される
>>> # identity が異なるので False が返されている。
>>> obj1 == obj2
False
>>>
>>> # identity が同じなら true が返される。
>>> obj1 == obj1
True


Python Hashes and Equality

Python における等式 == は大抵の人が認識しているよりも複雑ですが、根本的には __eq__(self, other) メソッドを実装しなければならないということです。
Equality in Python is more complicated than most people realize but at its core you have to implement a __eq__(self, other) method.

(Omitted)
中略

クラス定義時に継承するオブジェクトを指定しなければ、これらの __eq__(self, other), __ne__(self, other) などのメソッドは object クラスから継承されます。object クラスで定義されているメソッドでは2つのインスタンスを identity 同一性によって比較します。つまり2つのインスタンスが同じものである時のみ、等しいと判断します。
By default, those methods are inherited from the object class that compares two instances by their identity – therefore instances are only equal to themselves.


あまりちゃんと理解できていませんが、実際に cpython の object クラスの == の実装を見てみると、等しくなければエラーが返される様子が見えます。

        res = (self == other) ? Py_True : Py_NotImplemented;

cpython/typeobject.c

実装例 3. list クラス

list オブジェクトの == の実装。リスト内の全ての要素を比較したりしている様子が見えます。
cpython/listobject.c

どうやって組み込み関数のソースに当たればいいかは、stackoverflow 先生が教えてくれた。
Finding the source code for built-in Python functions?