TypeError: unhashable type: 'dict'
Asked Answered
B

7

376

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)
Bamford answered 7/11, 2012 at 6:47 Comment(4)
RTD docs.python.org/2.7/library/stdtypes.html#mapping-types-dictLiederkranz
It'd be useful to show the error report so we can see which line has the problem...Fawnia
Also relevant: Python hashable dicts; Check for mutability in Python?. 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?"Catapult
This issue might be relevant: #21986694Sanskritic
D
470

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
Drumm answered 7/11, 2012 at 7:3 Comment(10)
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.Dunsany
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) dictsTamishatamma
To convert the resulting frozenset back to dict, just call dict(the_frozenset).Kohn
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()))Stonewort
Although the answer mentions adding dicts to sets that case is not handled. Any takers?Sunder
@JasonHeiss In fact two dicts with different ordering of keys (but otherwise identical) come out as equal in Python (maybe not in other languages). Similarly frozensets from two dicts 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 dicts, 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 frozensets, already expensive enough).Stochastic
@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).Stochastic
@mikerodent You're absolutely right that dicts and sets are unordered. Not sure what I was thinking.Stonewort
@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.Stochastic
@mikerodent Yes indeed. I should just point to the docs that say this better than I'm managing to do. :) docs.python.org/3/library/stdtypes.htmlStonewort
S
42

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
Sigmoid answered 7/9, 2019 at 9:3 Comment(1)
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.Centering
D
7

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

Dougald answered 22/7, 2022 at 20:41 Comment(0)
B
2

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}]
Buckner answered 18/10, 2023 at 18:40 Comment(0)
P
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.

Petulant answered 5/3 at 13:56 Comment(0)
G
-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.

Gleaning answered 6/4, 2022 at 7:11 Comment(2)
It would be better if you explain why this is a solution.Soong
stackoverflow.com/help/how-to-answer has a great guide on what to do when answering a question.Dalrymple
M
-1

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)
Maclay answered 17/1 at 9:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.