We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Modelling Data with Dataclasses step 10 of 28
Freeze Nested Data into a Hashable Cache Key
You cache the result of a slow report, keyed by the settings that produced it. The settings arrive as a nested dict, so you wrap them in a frozen dataclass to use as the key. Building the key works. Hashing it does not:
from collections.abc import Hashable
from dataclasses import dataclass
@dataclass(frozen=True)
class CacheKey:
name: str
payload: Hashable
key = CacheKey("report", {"region": "eu", "days": [1, 2]})
hash(key) # TypeError: unhashable type: 'dict'
The generated hash of a frozen dataclass hashes its fields, and a dict has
no hash. frozen=True stops you from assigning a new payload, but it does
not freeze the dict inside. Convert the nested data into tuples and
frozensets, which are hashable, before you build the key:
deep_freeze({"b": 1, "a": [1, 2]})
# (("a", (1, 2)), ("b", 1))
What to write
Fill in deep_freeze(obj). Convert the value with the first row that
matches, and freeze every value inside it the same way:
| Input | Output |
|---|---|
a Mapping |
a tuple of (key, frozen_value) pairs, sorted by key |
a set or frozenset |
a frozenset of frozen elements |
a list or tuple |
a tuple of frozen elements, in the original order |
| a dataclass instance | (class_name, field_pairs), described below |
| anything else | the value itself |
- For a dataclass instance,
class_nameistype(obj).__name__.field_pairsis a tuple of(field_name, frozen_value)pairs indataclasses.fieldsorder, not sorted. Test for an instance withis_dataclass(obj) and not isinstance(obj, type). - Test
isinstance(obj, (list, tuple)), notisinstance(obj, Sequence), so a string stays a string. ANamedTuplematches the tuple row. - Keep duplicates in lists and tuples. Sets drop duplicates as usual.
- Do not change the input.
Mapping keys are strings. Every other value is a string, an integer, a
finite float, a boolean, bytes or None.
An empty mapping becomes (). A nested set becomes a frozenset, not a
sorted list. The tests check the container types as well as their
contents.
Complete CacheKey and solve
Decorate CacheKey with frozen=True and slots=True, and keep its
fields name: str and payload: Hashable.
In solve(config), build:
frozen, the result ofdeep_freeze(config).key, aCacheKeynamed"k"that holdsfrozen.twin, a secondCacheKeynamed"k"that holds a newdeep_freeze(config).raw_key, aCacheKeynamed"k"that holds the raw config, passed ascast(Hashable, config).casttells mypy to treat the dict asHashable. It does not change the object, so hashingraw_keystill fails.
Return these keys:
| Key | Value |
|---|---|
"frozen" |
frozen |
"raw_hashable" |
whether hash(config) succeeds |
"frozen_hashable" |
whether hash(frozen) succeeds |
"raw_key_hashable" |
whether hash(raw_key) succeeds |
"key_hash_stable" |
whether key is hashable and hash(key) == hash(twin) |
"key_equal" |
whether key == twin |
"mutation_error" |
what _mutation_error(key) returns |
Use the supplied _hashable and _mutation_error helpers, and do not
change them. FrozenInstanceError is a subclass of AttributeError, so
_mutation_error catches it first.
For the config in the example, the three hashability flags are False,
True and False. key_hash_stable and key_equal are True, and
mutation_error is "FrozenInstanceError".
Going further
This format loses a few differences. A list and a tuple with the same elements freeze to the same value. So do two dataclasses with the same class name and fields. For a real cache, check that the key keeps every difference your computation depends on.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Python reference solution
Sign in to track your attempts toward unlocking the reference solution.