__hash__の意味

5月5日の自分の日記で、間違った情報を書いてしまった。

__hash__の意味は、「辞書のキーで同じものとして扱える条件を決めるためのもの」であるということ。

__hash__メソッドは、ハッシュ値を計算するメソッドで、以下の2段階でキーが同じものであるかを判定している。以下の2つの条件を満たせば、辞書のキーとして同じものとして扱える。

  1. __hash__で返される値が同じ整数値である
  2. __eq__がTrueを返す。もしくは、__cmp__が0を返す

Jythonプログラミング』の例を以下のように書き換えてテストしてみる。以下では、__hash__を同値なオブジェクトで同じハッシュ値を返し、__eq__を同一な場合に同じオブジェクトであるとしている。

>>> from UserList import UserList
>>> class MyList(UserList):
...     def __hash__(self):
...         result = 0
...         for item in self:
...             result += item
...         return result
...     def __eq__(self, oth):
...         return id(self) == id(oth)
...
>>> a = MyList([1, 2, 3])
>>> b = MyList([1, 2, 3])
>>> dic = {}
>>> dic[a] = 1
>>> dic[b] = 2
>>> dic
{[1, 2, 3]: 2, [1, 2, 3]: 1}
>>> a.__hash__()
6
>>> b.__hash__()
6
>>> a.__eq__(b)
False

この例から分かるのは、ハッシュ値が同じでも、__eq__がFalseであれば、同じオブジェクトであると判定されない、つまり別のキーとして認識されるということである。


ビルトインのオブジェクトであれば、__eq__や__cmp__は、たいていのものは同値で判定されていて、その場合__hash__も「同値な場合に同じハッシュ値を返す」ように実装されているはずである。つまり自分で__hash__のみをオーバーライドする場合には「同値な場合に同じハッシュ値を返す」ように実装すべきである。


objectを継承して新スタイルクラスのユーザ定義クラスを作成する場合は、オーバーライドしない場合、objectクラスが基本となる。その場合、__hash__は「同一な場合に同じハッシュ値を返す」ようになっている。__eq__や__cmp__は定義されていないので、デフォルトでアイデンティティ値の比較になっている。

__hash__を同値で実装したら、__eq__もしくは__cmp__を同値で実装すべきであるし、逆に__eq__もしくは__cmp__を同値で実装したら、__hash__も同値で実装すべきである。

つまり、「__hash__」と「__eq__もしくは__cmp__」の判定の方法を合わせないとおかしくなるので注意する必要がある。