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}]