TypeError: unhashable type: 'list' の原因と直し方[Python]
辞書のキーに使ったり、集合に入れたりしたときに出るのがこのエラーになる。
TypeError: unhashable type: 'list'辞書型を入れたときはunhashable type: 'dict'、集合を入れたときはunhashable type: 'set'と、型名の部分が変わって表示される。原因はどれも同じになる。
ハッシュ可能とはどういうことか
Pythonの辞書と集合は、中身をハッシュ値で管理している。要素を入れた時点でハッシュ値を計算し、その値を使って高速に検索している。
ここで問題になるのが、あとから中身を書き換えられるオブジェクトだ。リストは要素を追加したり書き換えたりできるので、入れたときと後で見たときでハッシュ値が変わってしまう。そうなると、入れたはずのものが見つからなくなる。
そのためPythonは、変更できるオブジェクト(リスト、辞書、集合)をハッシュ不可にしている。
・ハッシュ可能。int、float、str、tuple、frozenset、None など変更できないもの ・ハッシュ不可。list、dict、set など変更できるもの
直し方1. tuple に変換する
いちばん多い解決策になる。リストをタプルにすれば、そのまま辞書のキーや集合の要素にできる。
key = [1, 2, 3]
d = {}d[key] = "value" # TypeErrord[tuple(key)] = "value" # OK集合に入れる場合も同じになる。
seen = set()seen.add(tuple(key))リストのリストから重複を除きたいときにも使える。
data = [[1, 2], [3, 4], [1, 2]]unique = list({tuple(x) for x in data})元の形に戻したければ、あとでリストに変換し直す。
unique = [list(x) for x in {tuple(x) for x in data}]直し方2. 入れ子のリストは再帰的に変換する
タプルにしても中身にリストが残っていると、やはり落ちる。
key = [1, [2, 3]]d[tuple(key)] = "x" # 中の [2, 3] が残っているので TypeError深い構造なら、再帰で変換する関数を用意する。
def freeze(obj): if isinstance(obj, list): return tuple(freeze(x) for x in obj) if isinstance(obj, dict): return tuple(sorted((k, freeze(v)) for k, v in obj.items())) if isinstance(obj, set): return frozenset(obj) return obj
d[freeze(key)] = "x"辞書をキーにしたい場合、順序が違っても同じものとして扱いたいなら、上のようにsortedを挟んでおく。
直し方3. 順序が関係ないなら frozenset
要素の並び順を区別したくない場合は、タプルよりもfrozensetが適している。
a = frozenset([1, 2, 3])b = frozenset([3, 2, 1])print(a == b) # Trueタプルだと(1, 2, 3)と(3, 2, 1)は別物になるので、用途に応じて選ぶ。
直し方4. JSONの文字列にする
構造が複雑で、キーとして一意に判別できればよい場合は、文字列にしてしまうのも手になる。
import json
key = {"b": 1, "a": [2, 3]}d[json.dumps(key, sort_keys=True)] = "value"sort_keys=Trueを付けないと、辞書の順序が違うだけで別のキーになってしまうので注意する。
pandas で出る場合
pandasでもこのエラーはよく出る。列の中身がリストになっているのが原因になる。
df.drop_duplicates() # リストを含む列があると TypeErrordf.groupby("tags").size() # tags 列がリストだと TypeError該当列をタプルに変換してから処理する。
df["tags"] = df["tags"].apply(tuple)df.drop_duplicates()リストを行に展開したいだけならexplodeが使える。
df = df.explode("tags")どの列が原因かわからないときは、型を確認すると早い。
for col in df.columns: if df[col].apply(lambda x: isinstance(x, (list, dict, set))).any(): print("問題の列:", col)自作クラスをキーにしたい場合
自分で定義したクラスは、標準ではidに基づくハッシュを持っているのでキーにできる。ただし__eq__を定義するとハッシュが無効になるため、__hash__も一緒に定義する必要がある。
class Point: def __init__(self, x, y): self.x, self.y = x, y def __eq__(self, other): return (self.x, self.y) == (other.x, other.y) def __hash__(self): return hash((self.x, self.y))データクラスならfrozen=Trueを指定するだけで、__eq__と__hash__の両方が自動で作られる。
from dataclasses import dataclass
@dataclass(frozen=True)class Point: x: int y: intまとめ
・辞書のキーと集合の要素は、あとから変更できないオブジェクトである必要がある。リスト、辞書、集合は使えない
・基本の対処はtuple()への変換。入れ子がある場合は再帰的に変換する
・並び順を区別したくないならfrozensetを使う
・pandasでは列の中身がリストになっているのが原因。apply(tuple)かexplodeで解決する
・自作クラスで__eq__を定義したら__hash__も定義する。データクラスならfrozen=Trueが簡単