user1805250
user1805250

Reputation: 3919

TypeError: unhashable type: 'dict'

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)

Upvotes: 387

Views: 987373

Answers (8)

Elie Eid
Elie Eid

Reputation: 981

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()
    )
)

Upvotes: 0

Greg7000
Greg7000

Reputation: 425

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.

Upvotes: 0

EdGaere
EdGaere

Reputation: 1474

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)

Upvotes: -1

cottontail
cottontail

Reputation: 23011

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

Upvotes: 3

Wotchin
Wotchin

Reputation: 190

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.

Upvotes: -1

Nathan Clement
Nathan Clement

Reputation: 1195

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

Upvotes: 10

Matteo Boscolo
Matteo Boscolo

Reputation: 577

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

Upvotes: 43

Lauritz V. Thaulow
Lauritz V. Thaulow

Reputation: 50975

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

Upvotes: 489

Related Questions