397

This piece of code is giving me an error

TypeError: unhashable type: dict

Can anyone explain to me what the solution is?

negids = movie_reviews.fileids('neg')
def word_feats(words):
    return dict([(word, True) for word in words])

negfeats = [(word_feats(movie_reviews.words(fileids=[f])), 'neg') for f in negids]
stopset = set(stopwords.words('english'))

def stopword_filtered_word_feats(words):
    return dict([(word, True) for word in words if word not in stopset])

result=stopword_filtered_word_feats(negfeats)
4

8 Answers 8

495

You're trying to use a dict as a key to another dict or in a set. That does not work because the keys have to be hashable. As a general rule, only immutable objects (strings, integers, floats, frozensets, tuples of immutables) are hashable (though exceptions are possible). So this does not work:

>>> dict_key = {"a": "b"}
>>> some_dict[dict_key] = True
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'dict'

To use a dict as a key you need to turn it into something that may be hashed first. If the dict you wish to use as key consists of only immutable values, you can create a hashable representation of it like this:

>>> key = frozenset(dict_key.items())

Now you may use key as a key in a dict or set:

>>> some_dict[key] = True
>>> some_dict
{frozenset([('a', 'b')]): True}

Of course you need to repeat the exercise whenever you want to look up something using a dict:

>>> some_dict[dict_key]                     # Doesn't work
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'dict'
>>> some_dict[frozenset(dict_key.items())]  # Works
True

If the dict you wish to use as key has values that are themselves dicts and/or lists, you need to recursively "freeze" the prospective key. Here's a starting point:

def freeze(d):
    if isinstance(d, dict):
        return frozenset((key, freeze(value)) for key, value in d.items())
    elif isinstance(d, list):
        return tuple(freeze(value) for value in d)
    return d
Sign up to request clarification or add additional context in comments.

10 Comments

Thanks, it works, however still get error if the value is a dict or list (unhashable) , now I am using hash(str(my_dict)), works fine for me.
just a note @StevenDu dictionaries do not guarantee order, so str(my_dict) could return two different strings for the same (or different, but equivalent) dicts
To convert the resulting frozenset back to dict, just call dict(the_frozenset).
It seems to me that frozenset(dict_key.items()) is potentially problematic in that two dicts with the same contents but different insertion order may not result in the same key. Adding a call to sorted() seems in order. E.g. frozenset(sorted(dict_key.items())) Additionally, frozenset seems like an odd choice given that sets are explicitly unordered. It probably works fine in practice, but tuple seems like a more logical choice to me. I went with tuple(sorted(dict_key.items()))
@JasonHeiss Not quite! Sorry: I feel a bit like a sort of hard taskmaster now. From Python (CPython) 3.7 dicts have also been ordered, by diktat of the BDFL: mail.python.org/pipermail/python-dev/2017-December/151283.html. But, as I say, a simple test shows that comparison still doesn't take account of order.
|
43

A possible solution might be to use the JSON dumps() method, so you can convert the dictionary to a string ---

import json

a={"a":10, "b":20}
b={"b":20, "a":10}
c = [json.dumps(a), json.dumps(b)]


set(c)
json.dumps(a) in c

Output -

set(['{"a": 10, "b": 20}'])
True

2 Comments

JSON is made up of "an unordered set of name/value pairs". So, changing the order of the name/value pairs will result in a "different" JSON string which will be registered as a new member of the set. In practice, this might not be a big deal since json.dumps() is likely predictable, but this is something to watch out for.
dumps has a sort_keys attribute, you could set it to True.
9

This happened to me because I was thinking in Typescript, and tried to set a python dictionary up like this:

thing = { 'key': 'value' }
other_thing = {'other_key': 'other_value'}
my_dictionary = { thing, other_thing }

Then I tried:

my_dictionary = { thing: thing, other_thing: other_thing }

...which still did not work

What ended up working was...

my_dictionary = { 'thing': thing, 'other_thing': other_thing }

Funny how used we get to the little syntax tricks from different languages...

Comments

4

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

Comments

0

There is a good package called frozendict

pip3 install frozendict
from frozendict import frozendict

And then either

d = frozendict(role="user", content=prompt)

or

d = frozendict(my_dict)

Comments

0

To get a set of dicts (which you can't create since dict are mutable) I recommend you use instead a dict of dict. You can then combine that with @StevenDu's suggestion

mydict: Dict[int, dict] = {}
d1 = {'a': 1, 'b': 2}
mydict[hash(str(d1))] = d1

Please note that instead of 'hash(str(d1))' you could use any custom way to get a unique key for your dict.

Comments

0

Used nested function to convert the dict at it nested dict to tuple.

def make_hashable(d: dict[str, Any]) -> tuple:
"""Convert a dictionary, which is un-hashable into a tuple which is a hashable form."""
return tuple(
    sorted(
        (k, make_hashable(v)) if isinstance(v, dict) else (k, v)
        for k, v in d.items()
    )
)

Comments

-1
def frozendict(d: dict):
    keys = sorted(d.keys())
    return tuple((k, d[k]) for k in keys)

Implementing the function above returns an immutable structure, and we prefer sorting the key to prevent out-of-order iterations.

2 Comments

It would be better if you explain why this is a solution.
stackoverflow.com/help/how-to-answer has a great guide on what to do when answering a question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.