This error occurs if you try to use a dictionary as a key for another dictionary (e.g. {{}: 1}
) or add a dictionary to a set (e.g. {{}}
) or check if a dict exists in set/dict_keys (e.g. {} in set()
) or use a column of dicts as a grouper in pandas groupby.
Possible solutions:
1. dict.items()
-> tuple
If insertion order of the dicts is important (which is lost if converted to frozensets), then refactor your code to convert the dict(s) to be used as dict key/added into a set into a tuple. For the example, the problem in the OP was that there was an attempt to use a dictionary (returned from word_feats
function) as a key for another dictionary. For example,
# dict as key of another dict
d1 = {'a': 1, 'b': 2}
d2 = {d1: 3} # <--- TypeError: unhashable type: 'dict'
d2 = {tuple(d1.items()): 3} # <--- OK
# dicts in a set
st = {d1, d2} # <--- TypeError: unhashable type: 'dict'
st = {tuple(x.items()) for x in (d1, d2)} # <--- OK
# membership tests
d1 in d2 # <--- TypeError: unhashable type: 'dict'
tuple(d1.items()) in d2 # True
So for the example in the OP, instead of returning a dict
, returning a tuple
solves the problem.
def word_feats(words):
return dict([(word, True) for word in words]) # <--- TypeError
def word_feats(words):
return tuple((word, True) for word in words) # <--- OK
This solution is useful if you were trying to cache a dictionary returned from function using the @functools.lru_cache()
decorator and got this error. Refactoring the function to return a tuple instead solves the error.
2. dict
-> str
Another way is to simply convert the dictionary into a string. Similar to tuple()
, it preserves insertion order. Then if the stringified key needs to be converted back into a dict, ast.literal_eval()
from the standard library can be used to recover it.
import ast
d1 = {'a': 1, 'b': 2}
d2 = {str(d1): 3} # {"{'a': 1, 'b': 2}": 3}
str(d1) in d2 # True
[ast.literal_eval(key) for key in d2.keys()] # [{'a': 1, 'b': 2}]
3. dict.items()
-> frozenset
Because frozensets don't preserve order, it's ideal if you wanted to add dicts into a set, perhaps to find unique dicts in a list. Then to recover the original dictionary from the frozensets, call dict()
on each frozenset. For example,
lst = [{1:3, 2:0}, {2:0, 1:3}, {2:3}] # list of dicts
set(lst) # <--- TypeError: unhashable type: 'dict'
st = {frozenset(d.items()) for d in lst} # convert each dict into a frozenset
# convert back into a list of unique dicts
[dict(d) for d in st] # [{2: 3}, {2: 0, 1: 3}]
As the output of the last line of code above shows, only one of lst[0]
and lst[1]
was correctly kept, since lst[0]==lst[1]
is True.
4. dict
-> json.dumps()
If the dicts are json serializable, then converting to json objects can be used to find unique dicts in a list too. If you want to make sure that the order of keys don't matter, use the sort_keys=
parameter of json.dumps()
. However, one important thing to note is that json requires the keys to be strings, so if the keys are numeric (as below), then converting to json and back into a dict may not recover the original dict if there are non-string keys.
import json
lst1 = [{1:3, 2:0}, {2:0, 1:3}]
[json.loads(j) for j in {json.dumps(d, sort_keys=True) for d in lst1}]
# [{'1': 3, '2': 0}]