is演算子と==演算子と特殊メソッド__eq__
pythonの比較演算には、is演算子と==演算子という同じような機能の演算子が存在する。
公式ドキュメントによると
- 5.9. 比較 - 5. 式 (expression) — Python 2.7.x ドキュメント
演算子 <, >, ==, >=, <=, および != は、二つのオブジェクト間の値を比較します。オブジェクトは同じ型である必要はありません。
...
演算子 is および is not は、オブジェクトのアイデンティティに対するテストを行います: x is y は、 x と y が同じオブジェクトを指すとき、かつそのときに限り真になります。 x is not y は is の真値を反転したものになります。
つまり、「==演算子はオブジェクトの値が同じであれば真」に,「is演算子は同じオブジェクトであれば(同じアドレスに格納されていれば)真」になる。
人間に例えると、同姓同名かどうかは==演算子で確認できるが、本人かどうかはis演算子で確認することになる。
int型の値1が格納された変数xとfloat型の値1.0が格納された変数yを比較すると、
x = 1 y = 1.0 print x, y, x == y, x is y
実行結果
1 1.0 True False
同じ値を持つ異なる型の場合は、数値は同じであるが異なるオブジェクトであるので、x == y
は真に,x is y
は偽になる。
一方、次のように値1を持つint型の同じインスタンスをxとyに代入すると、xとyは同じインスタンスを指し示しているので
x = y = int(1) print x, y, x == y, x is y
実行結果
1 1 True True
変数xとyが同じインスタンスを参照ているので、x == y
, x is y
ともに真になる。
ここまでは予定どうり。
ここで、結果は同じなのだが値1がintクラスのインスタンスである事を強調するために、x = 1
のように値を直接代入せずに、int型のコンストラクタ
を使ってわざわざintクラスのインスタンスを生成している点に注意。
では、同じ値を持つint型のインスタンスを別々に生成して、比較してみるとどうだろう。
x = int(1) y = int(1) print x, y, x == y, x is y
実行結果
1 1 True True
おやおや、わざわざ個別にint型のインスタンスを生成しているのに、x is y
も真になってしまった。
文字列(str型)の場合はどうだろう?
x = str("abc") y = str("abc") print x, y, x == y, x is y
実行結果
abc abc True True
int型と同じく、わざわざstr型のコンストラクタを使って別のインスタンスを生成したはずなのに、x is y
も真になってしまった。
個別にインスタンスを生成しても、何故、同じインスタンスが格納されてしまうのだろうか?
しつこく、今度はリスト型のインスタンスについても確認してみる。
x = list("abc") y = list("abc") print x, y, x == y, x is y
実行結果
['a', 'b', 'c'] ['a', 'b', 'c'] True False
お~でかけですか~、レレレのレ〜!
リスト型の場合には、ちゃんとx is y
も偽になる。
ちなみに、リスト型のコンストラクタの引数に文字列を指定すると、文字列が1文字づつ分解されてリスト型の要素になる。
コレクション型に分類される辞書型やタプルについても。
x = tuple("abc") y = tuple("abc") print x, y, x == y, x is y x = {"key1":"value1", "key2":"value2"} y = {"key1":"value1", "key2":"value2"} print x, y, x == y, x is y
実行結果
('a', 'b', 'c') ('a', 'b', 'c') True False {'key2': 'value2', 'key1': 'value1'} {'key2': 'value2', 'key1': 'value1'} True False
ここでは、辞書型のコンストラクタ を使うとわずらわしいコードになってしまうので、辞書型のインスタンスの生成にリテラル表記を使って記述している。
コレクション型に分類される型の場合には、ちゃんとx is y
も偽になるようである。
念のために、逆に、xとyが同じインスタンスを指している場合についても確認してみよう。
x = y = list("abc") print x, y, x == y, x is y x = y = tuple("abc") print x, y, x == y, x is y x = y = {"key1":"value1", "key2":"value2"} print x, y, x == y, x is y ['a', 'b', 'c'] ['a', 'b', 'c'] True True ('a', 'b', 'c') ('a', 'b', 'c') True True {'key2': 'value2', 'key1': 'value1'} {'key2': 'value2', 'key1': 'value1'} True True
同じインスタンスを指しているので、正しく、x == y
, x is y
ともに真になる。
これはどういう事かというと、推察するに、数値型や文字型などの単純型の場合には同じ値を代入するとメモリーを節約するためにpython内部で勝手に最適化がおこなわれ、同じ値のインスタンスは異なる変数に対して共有されるようになっている。
単純型の場合にはこれで問題は発生しないが、コレクション型などの複合データ型の場合には異なる変数間で同じ値のインスタンスを共有してしまうと、
違うインスタンスを生成したはずなのに、一方の要素を変更すると他の変数の要素も変更されてしまう事になり不都合な事になってしまう。
このため、このような最適化がおこなわれない仕組みになっていると考えられる。
単純型と複合型については
結論をいうと、同じ値のインスタンスが異なる変数に対して共有されるかどうかはデータ型によって異なるという事になる。
そして、これは多分,処理系依存になっていてpythonのバージョンなどが変わったりする場合の動作は保障されなのであろう。
この辺の事情については、ベテランプログラマーにとってはある程度,推察可能であるが、初心者にとってはわかりづらいだろうと思い、こんなまわりくどくぐだぐだと説明してしまった。
ユーザ定義のクラスの場合はどうだろう。
class MyCls(object): pass x = MyCls() y = MyCls() print x, y, x == y, x is y
実行結果
<__main__.MyCls object at 0x0238C570> <__main__.MyCls object at 0x0238C590> False False
空のクラスを定義して、そのインスタンスを比較するとx == y
, x is y
ともに偽になる。
クラスのインスタンスが同じ値をもつかどうか判断する必要がある場合には特殊メソッド__eq__
を実装する事になっていて、x==y
が実行される場合には、x.__eq__(y)メソッドが呼ばれる仕組みになっている。
そして、__eq__メソッドが実装されていない場合は、==の結果は常にFalseになるようだ。
例えば、名前と年齢をメンバーとして持つPersonクラスの場合には、名前と年齢の値が同じであれば同じ値だと判断するのが妥当であると思うので、以下のようなクラスを実装する事になるだろう。
class Person(object): def __init__(self, name, age): self.name = name self.age = age def __eq__(self, other): return self.name == other.name and self.age == other.age
同じ値を持つ異なるPersonクラスのインスタンスを比較すると
x = Person("gudon", 99) y = Person("gudon", 99) print x, y, x == y, x is y
実行結果
<__main__.Person object at 0x024CC690> <__main__.Person object at 0x024CC5F0> True False
変数xとyが同じインスタンスを参照している場合には
x = y = Person("gudon", 99) print x, y, x == y, x is y
実行結果
<__main__.Person object at 0x023FC650> <__main__.Person object at 0x023FC650> True True
ところで、公式ドキュメントには「x==y が真である場合、暗黙のうちに x!=y が偽になるわけではありません。」とあって
__eq__メソッドにはこれとペアになる__ne__メソッドを実装する必要がある。
そうしないと、x==y
が真であるのに x!=y
も真になる事もありうる。
実際に、上記のコードで定義したPersonクラスのインスタンスに対して以下を実行すると
print x == y, x != y
実行結果
True True
__ne__メソッドを実装していない場合にはTrueを返すようで、x==y
が真であるのに x!=y
ともTrueになってしまう。
従って、正しくはPersonクラスの定義に以下のメソッドを追加する必要がある事になる。
def __ne__(self, other): return not self.__eq__(other)