350

This piece of code is giving me an error 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)
yungmaz13
  • 139
  • 11
user1805250
  • 3,549
  • 2
  • 13
  • 4
  • 2
    RTD http://docs.python.org/2.7/library/stdtypes.html#mapping-types-dict – iMom0 Nov 07 '12 at 06:59
  • 4
    It'd be useful to show the error report so we can see which line has the problem... – drevicko Feb 17 '15 at 22:44
  • Also relevant: [Python hashable dicts](https://stackoverflow.com/questions/1151658); [Check for mutability in Python?](https://stackoverflow.com/questions/4374006/). It would probably be better to write a new canonical from scratch to cover the general isue of "what can be used as a dictionary key? How can I work around the limitations?" – Karl Knechtel Dec 29 '22 at 01:20

4 Answers4

452

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
KeepCalmAndCarryOn
  • 8,817
  • 2
  • 32
  • 47
Lauritz V. Thaulow
  • 49,139
  • 12
  • 73
  • 92
  • 3
    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. – Steven Du Dec 16 '15 at 03:07
  • 11
    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 – K Raphael Mar 14 '17 at 16:38
  • 1
    To convert the resulting frozenset back to dict, just call `dict(the_frozenset)`. – user Oct 08 '18 at 21:47
  • 7
    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()))` – Jason Heiss Oct 31 '19 at 20:10
  • Although the answer mentions adding dicts to sets that case is not handled. Any takers? – rwst Dec 29 '20 at 08:59
  • @JasonHeiss In fact two `dict`s with different ordering of keys (but otherwise identical) come out as equal in Python (maybe not in other languages). Similarly `frozenset`s from two `dict`s with different ordering of keys. A simple test suffices to prove this. So it seems that `sorted` would be redundant. Plus, if for whatever reason you did want to deal with sorted `dict`s, in most use cases I'm pretty sure it would make sense to sort when adding new items (equality functions would always require making new `frozenset`s, already expensive enough). – mike rodent Sep 13 '21 at 18:47
  • @JasonHeiss Plus (if I may), there is nothing "odd" about choosing to convert to a set: by definition you cannot have two identical keys in a `dict`. `type(mydict.keys())` is actually `dict_keys`, but this is necessarily suitable for conversion into a `set` rather than a `tuple` (or `list`) (where you *can* have repeating elements). – mike rodent Sep 13 '21 at 18:53
  • @mikerodent You're absolutely right that dicts and sets are unordered. Not sure what I was thinking. – Jason Heiss Sep 14 '21 at 19:48
  • 1
    @JasonHeiss Not quite! Sorry: I feel a bit like a sort of hard taskmaster now. From Python (CPython) 3.7 `dict`s have also been ordered, by diktat of the BDFL: https://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. – mike rodent Sep 15 '21 at 06:41
  • @mikerodent Yes indeed. I should just point to the docs that say this better than I'm managing to do. :) https://docs.python.org/3/library/stdtypes.html – Jason Heiss Sep 16 '21 at 19:15
39

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
BlackFox
  • 773
  • 1
  • 8
  • 24
Matteo Boscolo
  • 529
  • 4
  • 5
  • 12
    JSON is made up of ["an unordered set of name/value pairs"](https://www.json.org/json-en.html). 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. – rinogo Apr 12 '21 at 18:22
  • Well this worked perfectly for my issue. Thanks – Jonathan Sep 27 '21 at 10:24
6

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...

Nathan Clement
  • 1,103
  • 2
  • 18
  • 30
-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.

Wotchin
  • 160
  • 6