Python: ユーザー定義クラスで TypeError: unhashable type: ‘xxx’

はじめに

Python3で TypeError: unhashable type: 'xxx' エラーに遭遇した際の調査メモ。

環境情報

本記事の内容は、以下環境で試しました。

  • Windows10 Home edition
  • Python 3.7.1(Anaconda)

TypeErrorが発生しないケース

以下コードを実行すると

class Person:
    def __init__(self, name):
        self.name = name

x = Person("Taro")
y = Person("Taro")
print(f"x == y : {x == y}")
z = {x, y} # 集合を作成
print(len(z))

以下出力が得られます。

x == y : False
2

ここでのポイントは以下2点。

    •  __eq__をオーバーライドしていないため、== の結果はFalse
    • __hash__もオーバーライドしていないめ、集合要素数は2

TypeErrorが発生するケース

上記コードで==の結果をnameの比較にしたかったので、__eq__メソッドだけオーバライドしてみると…

class Person:
    def __init__(self, name):
        self.name = name

    def __eq__(self, other):
        return self.name == other.name

x = Person("Taro")
y = Person("Taro")
print(f"x == y : {x == y}")
z = {x, y}  # 集合を作成
print(len(z))

実行すると、以下出力とエラースタックが出ました。あれまorz.

x == y : True
Traceback (most recent call last):
  File "C:/Users/xxx/samle.py", line 12, in 
    z = {x, y}  # 集合を作成
TypeError: unhashable type: 'Person'

Pythonの__hash__関数のドキュメントをよく読むと以下の通りで、

…クラスが __eq__() を定義していても __hash__() を定義していないなら、そのインスタンスはハッシュ可能コレクションの要素として使えません。…

…__eq__() をオーバーライドしていて __hash__() を定義していないクラスでは、…そのクラスのインスタンスのハッシュ値を取得しようとすると適切な TypeError が送出され…

__eq__だけ定義してても__hash__定義してないとsetdictのようなハッシュを使ったコレクション型として扱えないということがTypeErrorが出た原因でした。

TypeErrorを直したケース

上記ドキュメントの記載を踏まえ、以下の通り__hash__関数を追加します。

class Person:
    def __init__(self, name):
        self.name = name

    def __eq__(self, other):
        return self.name == other.name

    def __hash__(self):
        return hash(self.name)

x = Person("Taro")
y = Person("Taro")
print(f"x == y : {x == y}")
z = {x, y}  # 集合を作成
print(len(z))

実行すると以下の通り、TypeErrorは発生しなくなり、setはname=”Taro”で重複した要素が除外されるようになりました。

x == y : True
1

まとめ

  • クラス定義で__eq__だけ実装して__hash__実装してないと、setやdictに入れたときにTypeErrorが発生する
  • Python公式ドキュメントobject.__hash__()に本記事で抜粋したこと以外にも重要なことが記載されているので、しっかり読んでおいたほうがいい。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA