Complex keys in dictionaries in Python
Let’s imagine you want to store list or dictionary as a key in python dict. And you are going to write the code like this:
dict_key = {'a': 3, 'b': 2}
stat = {}
stat[dict_key] = 1
It’s a bad code because your are trying to use mutable object as a key in dictionary. And Python will check it and this code will throw an error:
TypeError: unhashable type: 'dict'
Let’s consider how to use complex objects and data structures as dict keys in Python.
Let’s read the documentation and we see that Python doesn’t allow to store dict and list as dictionary or set key.
The reason is that dict and list are mutable and Python protect you from errors like this:
dict_key = {'a': 3, 'b': 2}
stat = {}
stat[dict_key] = 1
# Change the key and you'll not find it on dictionary
dict_key['c'] = 3
print(stat[dict_key])
Convert Dict to frozenset before use as dictionary key
Use frozenset for dictionary key-value list.
Let’s have a look at the example:
dict = {'a': 3, 'b': 2}
dict_key = frozenset(dict.items())
stat = {}
stat[dict_key] = 1
If you want to search you should also use frozenset
print(stat[dict_key])
Convert List to tuple before using as dictionary key
Let’s have a look how to do it:
list = [1, 2, 3]
list_key = tuple(list)
stat = {}
stat[list_key] = 1
If you want to search you should also use tuples:
print(stat[list_key])
# prints 1
Note: if you change order of elemenets in list you’ll not find it in dictionary. Use frozen set for it
For Objects you have to implement hashable methods
What happens if you to use your Class objects as key? By default Python uses method id() as a key. So every object will be treated as a new key. That’s not what you want to have.
Let’s have a look:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
stat = {}
stat[Point(1, 2)] = 1
print(stat.get(Point(1, 2)))
# prints None
For proper work it should implement methods hash and eq
Let’s do it and see the result:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __hash__(self):
return self.x ^ self.y
def __eq__(self, other):
return other and self.x == other.x and self.y == other.y
stat = {}
stat[Point(1, 2)] = 1
print(stat.get(Point(1, 2)))
# prints 1
Recap:
- Use frozendict(dict.items()) if you want to store dict as a key in other dictionary or set;
- Use tuple(list) if you want to store list as a key in dictionary;
- Implement hash and eq if you want to use your Class objects as dictionary keys. Also don’t forget that it should be immutable.
Stay tuned, check out my blog for more useful tips and tricks.