いつもは勉強したことのまとめ的な記事とかネタっぽい記事を書いている事が多いのですが、PythonのアドベントカレンダーっぽくPython言語の小ネタ的な事を記事にしてみようと思います。
"=="演算子と"is"演算子の違い
具体的に2つの違いとしては
・"=="演算子はオブジェクトの同等性をチェックするもの
・"is"演算子はオブジェクトの同一性をチェックするもの
です。
ここから先はコードで実際に示した方が分かりやすいので、コードに移ります。Pythonのインタプリタモードを使って説明します。
まずは変数list_aというリストを作って、そのlist_aを指す変数list_bを作ります。
>>> list_a = [0, 1, 2, 3]
>>> list_b = list_a
当然、中身を見ても全く同じものが入っています。
>>> list_a
[0, 1, 2, 3]
>>> list_b
[0, 1, 2, 3]
list_a, list_bを"=="演算子と"is"演算子を使って比較すると
>>> list_a == list_b
True
>>> list_a is list_b
True
どちらもTrueが返ってきました。
次に変数list_aと全く同じ中身の変数list_cを作成します。
>>> list_c = list(list_a)
>>> list_c
[0, 1, 2, 3]
これを同じように"=="演算子と"is"演算子を使って比較すると
>>> list_a == list_c
True
>>> list_a is list_c
False
"is"演算子ではFalseが返ってきました。list_aとlist_cは中身が同じであったとしても、これらは別のオブジェクトだからです。試しにlist_aの中身を更新してlist_b, list_cがどうなったのかを確認してみます。
>>> list_a[0] = 1
>>> list_a
[1, 1, 2, 3]
>>> list_b
[1, 1, 2, 3]
>>> list_c
[0, 1, 2, 3]
list_aの中身を更新することで同じオブジェクトを指しているlist_bは更新され、別のオブジェクトであるlist_cはそのままです。
このように"=="演算子はオブジェクトの中身が同じ場合にTrueを返し、"is"演算子はオブジェクト自体が同じ場合にのみTrueを返します。
補足1 Nanの比較について
※これにハマる人が多いので、記載しておきます。
PythonにおけるNanに関してはどの比較演算子(==, !=, <, >, <=, >=)を使ってもFalseが返ってくるという性質があるので、Nanかどうかを判定するには"is"演算子かnumpyのisnan()を使いましょう。
(Nan自体が実態の無いものだから比較はできないってことでしょうかね?いや、知らんけど)
>>> a = np.nan
>>> a == np.nan
False
>>> a is np.nan
True
>>> np.isnan(a)
True
>>> np.nan == np.nan
False
>>> np.nan is np.nan
True
>>> np.isnan(np.nan)
True
(2021/12/21 追記)
コメントで指摘して頂いたのですが、以下のようなケースもあるのでNanの比較は"is"演算子ではなくisnan()を使った方が良いようです。
>>> b = float('Nan')
>>> b is np.nan
False
>>> np.isnan(b)
True
>>> c = np.nan + 1
>>> c is np.nan
False
>>> np.isnan(c)
True
補足2 多次元配列について
※これもハマる人が多いので、記載しておきます。
ちょっと今回の趣旨とは若干ずれるのですが、これもハマる人が多いので、記載しておきます。
多次元配列、いわゆるオブジェクトの中にオブジェクトがある場合ですが、外側のオブジェクトと内側のオブジェクトがある事に注意が必要です。
今度は二次元配列でlist_aを作成し、先ほどと同じようにlist_cをlist_aと同じ内容で新規に作成します。
>>> list_a = [[0, 1], [2, 3]]
>>> list_c = list(list_a)
>>> list_c
[[0, 1], [2, 3]]
同じように"=="演算子と"is"演算子で比較してみましょう。
>>> list_a == list_c
True
>>> list_a is list_c
False
先程と同じ結果になりました。次はオブジェクトの中のオブジェクトを比較してみましょう。
>>> list_a[0] == list_c[0]
True
>>> list_a[0] is list_c[0]
True
見ての通り内側のオブジェクトの比較で"is"演算子でもTrueが返って来ました。どうやら外側のオブジェクトは別でも内側のオブジェクトは同じオブジェクトのようです。
なので、list_aの内側のオブジェクトを変更すると
>>> list_a[0][0] = 1
>>> list_a
[[1, 1], [2, 3]]
>>> list_c
[[1, 1], [2, 3]]
list_aだけでなくlist_cの内容も更新されました。このように多次元配列の場合、外側のオブジェクトと内側のオブジェクトがある事に気をつけましょう。
このように、外側のオブジェクトのみを新しく作成して内側のオブジェクトは元のオブジェクトを参照する事をシャローコピー(浅いコピー)、外側も内側も新しいコピーを作成する事をディープコピー(深いコピー)と言います。
ディープコピーはcopyモジュールで定義されているdeepcopyを使って深いコピーを作成します。
>>> list_a
[[1, 1], [2, 3]]
>>> import copy
>>> list_d = copy.deepcopy(list_a)
>>> list_d
[[1, 1], [2, 3]]
>>> list_a is list_d
False
>>> list_a[0] is list_d[0]
False
まとめ
・"=="演算子は2つのオブジェクトの中身が同等の場合にTrueを返す。
・"is"演算子は2つのオブジェクトが同一の場合にTrueを返す。
・Nanとの比較には"is"演算子isnan()を使おう。
・多次元配列には外側のオブジェクトと内側のオブジェクトがある事に気をつけよう。