Pythonに限らず、他の言語でもこういった概念がある言語は多いのですが。ここではPythonを例に上げます。
#「同一」と「同値」
「同じ」という言葉には、2つの意味があります。「同一」と「同値」です。
##「AさんとBさんは、同じ家に住んでいます」
どういう意味でしょう?
大抵の人は、AさんとBさんが同居している、と考えるでしょう。
つまり、AさんとBさんが「同一の」家に住んでいる、ということです。
##「AさんとBさんは、同じ服を着ています」
どういう意味でしょう?
AさんとBさんが「同一の」服を着ている、つまり、1枚の服を2人で着ている、と考える方は滅多にいないと思います。
AさんとBさんが、それぞれ服を着ていて、Aさんが着ている服とBさんが着ている服が、例えば同じメーカーの同じデザインの服だったりしている、と考えるのが自然です。
もしかしたら物自体は別かもしれないけれど、何らかの意味で、同じと呼べる。
こういった関係を同値性、あるいは等価性と呼んでいます。
##同値性の判定には実装が必要
ところで、整数のような簡単なものではなく、服のような複雑なものだと、何をもって「同値」とみなすのかは、考えないといけないですね。
服のサイズも条件に含めるなら、AさんとBさんでサイズが違った場合は、他がどんなに似通っていても同値とはみなされません。
服のサイズを条件に含めないなら、もしあなたがアパレルの店員をしていて、お客さんに「不良品だから同値の服と取り替えろ!」って言われた場合、サイズ違いを持ってくるかもしれません。
また、一応書いておくと、多くの場合は「同一」ならば「同値」になります。
ただし、何をもって「同値」とみなすかに依りますので、必ずしもそうとは限りません。
#なぜ同一と同値の区別が必要なのか?
##「AさんとBさんは、同じ車に乗っています。Aさんの車は、勢いよく壁に衝突しました」
Bさんは無事でしょうか?
AさんとBさんが、同一の車に乗っていたならば、Bさんも無事ではないでしょう。
AさんとBさんが、同値の(同じ車種の)車に乗っていても、同一の車には乗っていなければ、Bさんは何の関係もありません。
##「変数aと変数bは、同じリストを指しています。変数aのリストから、要素をひとつ削除しました」
変数bのリストからは、要素が削除されたでしょうか?
変数aと変数bが同一なら、bのリストからも要素は削除されています。
変数aと変数bが同値であっても、同一でなければ、bのリストは保たれたままです。
このように「同一」と「同値」は似て非なる概念なのですが、通常は、同一よりも同値の方が、よく使うのではないでしょうか。
同一と同値の区別が必要となるのは、多くの場合は、比較対象がリストのような変更可能な(mutable)オブジェクトのときです。
Pythonのようにプログラマの見えないところでメモリを確保してくれる処理系では、変更不可な(immutable)オブジェクトが、実際は同じオブジェクトを指しているのかどうかを考えなければならないことは滅多にありません。そのため、変更不可なオブジェクトでは、isを使うことは滅多にありません。
#Pythonにおいて、同一性、同値性を調べる演算子は何か?
Pythonでは、同一であることを調べる演算子が is で、 同値であることを調べる演算子が == です。
また、同一でないことを調べる演算子は is not で、同値でないことを調べる演算子は != です。
##==と!=のオーバーロード
演算子 ==, !=は特殊メソッド名によってオーバーロードが可能です。(つまり、特殊な名前を持ったメソッドを作れば、同値性の比較方法を定義することができます。)
同一性を調べる演算子のオーバーロードはできません。
Python2では、 == をオーバーロードする特殊メソッド名は2種類あります(ただしPython 2.1以降)。Python3では、片方が廃止されて1種類となりました。
Python2(ただしPython 2.1以降)でも3でも共通で使える特殊メソッド名は == に対応するものが__eq__(self, other)
で、 != に対応するものが__ne__(self, other)
です。
今後作るなら、Python 2.1以前で動かす予定がないなら、迷わずにこちらを使ってください。
コード例を示します。
class Car:
def __init__(self, maker, model, color, owner):
self.maker = maker
self.model = model
self.color = color
self.owner = owner
def __eq__(self, other):
try:
if (self.maker == other.maker and self.model == other.model and
self.color == other.color):
return True # ownerは違っていても同じ車とみなす
except AttributeError:
pass
return False
def __ne__(self, other):
return not self == other
my_car = Car("Nissan", "GT-R", "Red", "my name")
your_car = Car("Nissan", "GT-R", "Red", "your name")
his_car = Car("Nissan", "Note", "Blue", "his name")
my_garage = [my_car]
print(my_car == your_car) # ==> True
print(my_car == his_car) # ==> False
print(my_car is my_car) # ==> True
print(my_car is my_garage[0]) # ==> True
print(your_car is my_garage[0]) # ==> False
print(your_car == my_garage[0]) # ==> True
また、Python2でのみ使える特殊メソッド__cmp__(self, other)
は、 <, <=, ==, !=, >=, >の6つの演算子を一気に定義する演算子で、`__cmp__(self, other)`が負になれば < を、0になれば == を、正になれば > を意味します。
3で使えないので全くおすすめしませんが、Python2.7系でも、intは__cmp__
で実装されているっぽいので、念の為紹介します。
蛇足ですが。新しい方式だと、大小の比較を実装するには、__eq__
の他に__ne__
, __lt__
, __le__
, __ge__
, __gt__
を定義しないといけませんが、Python 2.7以降および3では、functools.total_orderingを使うことで、__eq___
と、 __lt__
, __le__
, __ge__
, __gt__
のうちのどれかを定義すれば、あとは勝手にやってくれます。
#その他
##Pythonの組み込みクラスで、同一なのに同値とならない場合はありますか?
浮動小数点型のnan (not a number)は、何と比較しても同値となりません。なので、自分自身と比較しても同値となりません。
nan = float("nan")
print(nan is nan) # ==> True
print(nan == nan) # ==> False
##Noneと比較する場合は、==ではなくisを使うべき、と聞いたことがあります。なぜですか?
==だと、上述の通り、ユーザが好きに実装できてしまうので、場合によっては、TypeErrorやAttributeErrorなどの例外が出たり、NoneでないのにTrueを返したり、処理が重かったりするかもしれません。
ですが、isで比較すると、確実にその値がNoneであるか否かを比較できます。
ただしこれは、Noneの値を持つオブジェクトがただひとつしか存在しないことが言語仕様で保証されているがゆえにできることです。同値のオブジェクトが複数存在しうる場合は、このような使い方はトラブルのもとです。
##isか==か、どっちが処理が早いですか?
基本的に、isの方が早いです。ただし、どちらを使ってもいいという場面は少ないでしょう。
##int同士をisで比較すると、同一になりました。intの比較はisでもいいということですか?
同じ値でも、同一になる場合とならない場合があります。何か特殊な用途で同一性を調べたい場合を除いて、intの比較には==を使ってください。
##str同士をisで比較すると、同一になりました。strの比較はisでもいいということですか?
同上。
##isって、JavaScriptの===みたいなもんでしょ?
違います。JavaScriptの===は、型変換なしでも同値かどうかを調べる演算子なので、あくまで同値かどうかを調べています。Pythonのisは、同一であることを調べています。
同じようなことをPythonでするには、例えばtype(a) is type(b) and a == b
などとすることを考えてください。