Python “==” か “is” か (2022/02/19追記あり)

Pythonで異なる2つの変数の値が同じかどうか判定するに当たって、”==”を使うか”is”を使うか悩みがちだったのでまとめてみました。

“==”

基本的にはこのオペレーター”==”を使用していました。
Pythonを使っている方には当然ですが、オペレーター”=”は変数代入の意味になります。
イコールかどうか、をチェックする場合には”==”を使用します。

>>> a = 1
>>> b = 1
>>> a == b
True

四則演算から進むと普通その流れでこのオペレーターを使うかと思います。

“is”

でも色々人のコードを見ていると、同様の場所で”is”を使用しているものをちらほら見かけます。”is”でも同じ結果が出る様です。

# aとbは同じ
>>> a is b
True

どちらも同じような動きをしている為、どう違うか意識せず自分も混同し始めていました。

混ぜるな注意

今回改めて調べて分かりましたが、結論から言うと、意味する所は結構異なりますので混同注意です。

データが「同じ」の意味

Pythonの公式documentation operator — Standard operators as functions
によると
a == b はEquality
a is b はIdentity
とあります。

Equality

これは素直に値が同値かどうか、と言う話です。
“==”はその両端の比較対象が文字であれ数値やリストであれ、ただ「値」が同じである事を意味します。

Identity

しかし、Identityと言うともう少し深そうです。

そう、簡単に終わる訳がありません。Identity探しに旅に出る人すら居るご時世です。
ただ値が同じならよしとされる訳がありません。

探していると、分かりやすい説明が前に買ったこの本「Lutz, Mark. Learning Python. O’Reilly Media」の6章にありました。
この本、海外のサイトでもマストアイテムとしてよく上がっている気がします。読みこなせていない私が言うのもなんですが、説明は分かりやすいと感じています。

翻訳の上引用「”is”オーペレーターは”==”とは違って、identityをチェックします。両方の変数が同じobjectを指し示す場合のみTrueを返します。」

変数は矢印のイメージ

そう、改めて書きますが、Pythonの変数のイメージは先エントリで書いた様に矢印のイメージです。

簡単にまとめると

“==”は左右の値が同じでさえ有ればTrueを返すけれども、”is”は比較対象の左右の変数がどちらも「同じ場所にある(同idの)データ」を指していないとTrueが返ってこない、と言う話になります。同じ場所のデータなので結果的に値は同じとなり、混同しがち。

例示

リストを別の変数に保存すると、メモリ上別データになりますので、上記の通り、”==”と”is”で別の結果が返ってきます。

>>> A = [0, 1, 2]
>>> B = [0, 1, 2]

>>> A == B
True

>>> A is B
False

ここで”==”と”is”が同じ物と思っているとなんで???となってしまいます。
理由は上記の通り。
うん、分かりやすい。世の中こうでなくっちゃ。

今まで見かけていた、”is”が使用されていたコードも実はidentityを確認する意図だったのかも知れません。
次に見かけた時はもっとちゃんと確認しようと思った次第。

それはそれとして、ここまで読んだ辛抱強くかつ慧眼な皆様は逆にあれ?と思った事でしょう。
最初の例はどっちもTrueだったじゃん、と。

最初のサンプル

>>> a = 1
>>> b = 1

>>> a == b
True

>>> a is b
True

おっと、確かに仰るとおり。

説明と矛盾があるじゃん!!!

でも大丈夫。
そんなあなたへの回答もこの本「Lutz, Mark. Learning Python. O’Reilly Media」の6章には書いてあります。この方、きっとPythonの神の1人ですね。

翻訳の上引用「小さなintegersやstringsの場合、一旦置いといて(cacheに保存)して、再使用されます」
と言う事で、リストなどは全く別のIdentityになるのでFalseが返りますが、”1″等の小さなintegerや同様に小さなstringの場合は同じメモリ上のデータを再利用する為、Identityが同じになってしまう訳です。

idを調べてみる

それぞれのidentityを調べてみましょう。

多分皆さんが実行するとその度毎違うidが割り当てられると思いますが、私の時はこんな感じ。

>>> id(a)
4543454992
>>> id(b)
4543454992

>>> id(A)
4547385856
>>> id(B)
4547041552

小さいintegerのaとbは同じidでしたが、リストのAとBは異なるidである事が分かります。

どこまでが小さいintegerなのか?

と言う事で、どこまでなら同じidを共有出来る小さいintegers(small integers)なのか、調べてみましょう。

>>> a, b = 1, 1
>>> while a is b:
...     a += 1
...     b += 1
... 
>>> print(a - 1, b -1)
256 256

while文でFalseが出た数字が最後のa, bなので1を引いたa, bが同idのintegerになります。式はスマートではありませんが、分かりました。(コードに一部不正確な所があったので修正: 2022/02/19)

と言う事で256まで、2の8乗で1バイトで収まる文字まで、と考えてよさそうです。

確認

と言う事で念の為確認。

>>> c = 256
>>> d = 256
>>> id(c)
4468170480
>>> id(d)
4468170480

>>> e = 257
>>> f = 257
>>> id(e)
4471904304
>>> id(f)
4471904240

おお、確かに256迄は同じidで257になった途端別のidになりました。
無事に想定道理の結果が出てほっと胸をなで下ろしています。

ここで疑問が出てしまった。
コンピュータのデータの持ち方は0からスタートしているはずなので、255までが1バイトじゃないのだろうか?
この辺りその内分かったらまた更新します。
(以下の通り、只の仕様のようです。:2022/02/19)

小さいデータの範囲について(2022/02/19追記)

下記ツイートの流れでこの辺りの説明があったので備忘録として。

1つ目のツイートについては、上記で書いた様に256を超える数はidが異なるので、isオペレーターでチェックするとFalseが出る。

で、2つ目のツイートによると、この範囲は-5から256迄、そして特殊文字を含まない3桁迄の数字と文字(アルファベットと言う理解)との事。

確かに下記でやってみると、-5と出てきた。

>>> a, b = 1, 1
>>> while a is b:
...     a -= 1
...     b -= 1
... 
>>> print(a + 1, b + 1)
-5 -5

で、なぜ下は-5か?と言うと、それがPythonの仕様と言う事で。

The current implementation keeps an array of integer objects for all integers between -5 and 256, when you create an int in that range you actually just get back a reference to the existing object.

後半訳:-5から256の範囲で数字をcreate(データとしてインプットの意と言う理解)すると、既にあるidを再使用する。

とりあえず結論としては、if文等で条件分岐をする際にidが同じかをチェックするオペランドisを使うと思わぬエラーを起こす恐れがあるので、基本的には”==”を使用するのが良い、とは言えるだろう。

皆様の情報共有に感謝。

コメント

タイトルとURLをコピーしました