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


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

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


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

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

6.10. Comparisons

1. 比較演算子 is

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

a = [1, 2, 3]
b = a
c = [1, 2, 3]

a is b  # True  ... 同じオブジェクト
c is a  # False ... 別のオブジェクト


is 比較演算子は 2 つの変数に代入されたオブジェクトの identity, 同一性が等しければ True を返します。 次の2つの式は、等価です。

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]


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

a = [1, 2, 3]
b = a
c = [1, 2, 3]

id(a)
id(b)
id(c)
>>> a = [1, 2, 3]
>>> b = a
>>> c = [1, 2, 3]
>>> 
>>> id(a)
4360540680
>>> id(b)
4360540680
>>> id(c)
4360555080
>>> 


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 クラスの == の実装を見てみます。

static PyObject *
object_richcompare(PyObject *self, PyObject *other, int op)
{
    PyObject *res;

    switch (op) {

    case Py_EQ:
        /* Return NotImplemented instead of False, so if two
           objects are compared, both get a chance at the
           comparison.  See issue #1393. */
        res = (self == other) ? Py_True : Py_NotImplemented;
        Py_INCREF(res);
        break;

    ... 省略

cpython/typeobject.c

self, other にはオブジェクトへのポインタが格納されています。CPython で self == other とは Python で言えば self is other みたいなものです。

        res = (self == other) ? Py_True : Py_NotImplemented;
        // Python のコードに無理やり書き換えるなら...
        // res = True if self is other else NotImplemented


でも、等しければ True(Py_True) はわかるのですが、なぜ等しくないときは Py_NotImplemented を返してるのでしょうか?理解してないですが。Py_False を返してしまうと期待した動作をしてくれないみたいです.. 詳細はリンク先をどうぞ。
Issue 1393: function comparing lacks NotImplemented error - Python tracker

実装例 3. list クラス

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

static PyObject *
list_richcompare(PyObject *v, PyObject *w, int op)
{
    PyListObject *vl, *wl;

    ... 省略

    vl = (PyListObject *)v;
    wl = (PyListObject *)w;

    ... 省略

    /* 先頭から見て最初に要素が異なるインデックスを探す */
    for (i = 0; i < Py_SIZE(vl) && i < Py_SIZE(wl); i++) {
        int k = PyObject_RichCompareBool(vl->ob_item[i],
                                         wl->ob_item[i], Py_EQ);
        if (k < 0)
            return NULL;
        if (!k)
            break;
    }

    ... 省略

cpython/listobject.c

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

singleton を比較するときはに is を使う

# OK
if x is None:
    ...

# NG
if x == None:
    ...

None のような singleton を比較するときは、必ず is もしくは is not を使ってください。決して == 演算子を使ってはいけません。
Comparisons to singletons like None should always be done with is or is not, never the equality operators.
Programming Recommendations - PEP 8


個人的に疑問を感じています。まず第一に、同値性の比較演子 == に同一性の比較演算子 is を使うのは、ちょっと変な感じもします。また第二に、int や long の違いについては Python の実装のされ方を意識せずにコーディングしてるのに、 None が singleton であるかどうかについては Python の実装のされ方を意識してコーディングしないといけないというのは、ちょっと不思議な感じです。PEP 8 に記載されているので従ってはいますが。

下記のブログで議論された英文のブログが紹介されています。
Pythonでは None の比較は == ではなく is を使う