A dictionary can be modified at any time after it is created. Because of that, Python cannot compute a stable hash for it, and an object without a stable hash cannot be used as a dictionary key or stored in a set. This can become a limitation whenever a group of related values needs to identify something, such as a set of connection settings used to look up a cached resource.
For example, a dictionary holding connection settings can be read normally, but not used as a key:
config = {'host': 'localhost', 'port': 8080}
print(config['host']) # localhost
cache = {config: 'connection-1'} # TypeError
Reading values from the dictionary works, but using it as a key fails. The usual workaround is to convert the mapping into a tuple of sorted items, which is awkward to write and loses the mapping interface.
Since Python 3.15, the built-in frozendict provides a mapping that cannot be modified after creation, which allows it to be hashed.
config = frozendict(host='localhost', port=8080)
print(config) # frozendict({'host': 'localhost', 'port': 8080})
print(config['host']) # localhost
cache = {config: 'connection-1'}
print(cache[frozendict(port=8080, host='localhost')]) # connection-1
config['port'] = 9090 # TypeError
The lookup succeeds even though the second mapping lists its keys in the opposite order. The hash is derived from the set of items rather than their order. Attempting to change the mapping raises an error, since the methods that would modify it do not exist.
Besides item assignment, frozendict has no __delitem__, clear, pop, popitem, setdefault or update. Everything else from the mapping interface is available, including keys, values, items, and copy. The constructor accepts the same forms as dict, so it can be built from keyword arguments, an existing mapping, or an iterable of key and value pairs, and insertion order is preserved.
Leave a Comment
Cancel reply