We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Modelling Data with Dataclasses step 11 of 28
Predict Which Dataclasses Are Hashable
You are about to use seven record classes as dictionary keys, and you want
to know which ones raise TypeError: unhashable type before a user finds
out. The article Why Some Objects Can Go in a Set explained why a plain
dataclass has no hash and a frozen one does. Here you write a function
that predicts the answer from the class alone, without building an object.
solve("Partial")
# {"hashable": True, "reason": "eq-and-frozen", "hashed_fields": ["a"]}
What the decorator does to __hash__
For a class that does not define __hash__ itself, three options decide
what happens:
| eq | frozen | unsafe_hash | What the decorator does |
|---|---|---|---|
| True | True | False | Writes a __hash__ from the fields |
| True | False | False | Sets __hash__ to None |
| False | either | False | Leaves __hash__ alone, so the class keeps the one it inherits |
| either | either | True | Writes a __hash__ from the fields |
eq=True and unsafe_hash=False are the defaults. Identity has
eq=False and inherits from object, so it keeps the identity hash.
A generated __hash__ can still fail. It hashes the fields together, so a
field that holds a list raises TypeError. FrozenList has such a field.
What to write
Fill in hashability_report(cls). Do not create instances, and do not
change the classes. Apply these steps in order, and stop at the first one
that returns:
- If
getattr(cls, "__hash__", None)isNone, returnFalsewith the reason"eq-without-frozen". - If
"__hash__"is not a key ofcls.__dict__, the class inherited its hash. ReturnTruewith the reason"eq-disabled-identity-hash". - Collect the fields that take part in the hash, in
dataclasses.fieldsorder. A field takes part when itshashoption isTrue, or whenhashisNoneandcompareisTrue. A field withhash=Falsenever takes part. - Get the annotations with
get_type_hints(cls). For each field from step 3, takeget_origin(annotation) or annotation. If that islist,dict,setorbytearray, returnFalsewith the reason"unhashable-field-type". - Otherwise, return
Truewith the field names from step 3. The reason is"unsafe-hash"when_params(cls).unsafe_hashisTrue, and"eq-and-frozen"otherwise.
Return a dictionary with exactly the keys "hashable", "reason" and
"hashed_fields". hashed_fields is an empty list in steps 1, 2 and 4.
Keep the field names in declaration order, not sorted. Keep solve as it
is. It takes a name from REGISTRY and calls your function.
Expected reports
| Target | hashable | reason | hashed_fields |
|---|---|---|---|
| Mutable | False | eq-without-frozen | [] |
| Frozen | True | eq-and-frozen | ["a", "b"] |
| Identity | True | eq-disabled-identity-hash | [] |
| Unsafe | True | unsafe-hash | ["a"] |
| FrozenList | False | unhashable-field-type | [] |
| Partial | True | eq-and-frozen | ["a"] |
| Override | True | eq-and-frozen | ["a", "b"] |
Partial.cached has compare=False and the default hash=None, so it
takes part in neither == nor the hash. Override.b also has
compare=False, but hash=True puts it in the hash.
Two classes not to copy
hashable: True means hash() succeeds. It does not mean the class is
safe as a key.
Overridebreaks the rule that equal objects have equal hashes.Override(1, "x") == Override(1, "y")isTrue, but their hashes differ, becausebis in the hash and not in==.Unsafelets you change a field after you store the object in a set, and then the set cannot find it.
Type checking
Your code must pass mypy --strict. Read the hash through getattr, as in
step 1. _params(cls) returns the options the decorator received, so
_params(Unsafe).unsafe_hash is True. Its return type,
DataclassParams, only tells mypy which attributes the object has.
DataclassInstance is a type that exists only for the type checker and
means "any dataclass instance". That is why the starter imports it under
if TYPE_CHECKING: and writes it in quotes.
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.