Python Caching: TypeError: unhashable type: 'dict'

Viewed 1462

I am trying to implement a caching function in Python. The code looks like this:

def memoize(func):
    """Store the results of the decorated function for fast lookup
    """

    # Store results in a dict that maps arguments to results
    cache = {}

    def wrapper(*args, **kwargs):
        # If these arguments haven't been seen before, call func() and store the result.
        if (args, kwargs) not in cache:        
            cache[(args, kwargs)] = func(*args, **kwargs)          
        return cache[(args, kwargs)]

    return wrapper

@memoize
def add(a, b):
    print('Sleeping...')
    return a + b

add(1, 2)

When I run the code, I get TypeError: unhashable type: 'dict'.

What's wrong?

4 Answers

The key of a dict must be hashable. You provide an unhashable key (args,kwargs) since kwargs is a dict which is unhashable.

To solve this problem, you should generate a hashable key from the combination of args and kwargs. For example, you can use (assuming all values of args and kwargs are hashable)

key = ( args , tuple((kwargs.items())))

def memoize(func):
    """Store the results of the decorated function for fast lookup
    """

    # Store results in a dict that maps arguments to results
    cache = {}

    def wrapper(*args, **kwargs):
        # If these arguments haven't been seen before, call func() and store the result.
        key = ( args , tuple((kwargs.items())))
        if key not in cache:        
            cache[key] = cc = func(*args, **kwargs)          
            return cc
        return cache[key]

    return wrapper

@memoize
def add(a, b):
    print('Sleeping...')
    return a + b

print(add(1, 2))

This happens, because you are trying to put a dictionary as a key, which is a problem. You can use frozenset() to froze the dictionary, so that it would

In this line:

cache[(args, kwargs)] = func(*args, **kwargs)

you use kwargs which is dict as part of key. dicts are mutable and keys of dicts have to be immutable.

Dict is not hashable, which means that you cannot use it in operations that require hash of the objects. Using the object as a key in a dict is one of those things.

In your case in specific, the awards are a dictionary and you are trying to use it as a part of a key for another dictionary.

To make it work, you should create a function that receives the awards and turn it into a number or a string that acts has a fingerprint for any dictionary with the same content.

As a side note, this can also happen if you pass any dict as an argument, nameless, or keyword. If you chose to go with this solution, my advice is to do it in both args and kwargs and recursively check if you have any dict within your args.

As a second side note, on functools module, you have lru_cache for local cache, and you can use cachetools for cache operations and aiocache for an async cache that support the most popular backends other than local cache.

Related