We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Modelling Data with Dataclasses step 24 of 28
Diff Two Dataclass Records Field by Field
A user edits a profile, and the audit log should say which fields
changed. You have the record from before the edit and the one from after.
Write a function that compares two dataclass instances field by field. It
reports each change with its path, such as inner.x for a field of a
nested record.
solve("nested")
# {"error": "", "changes": {"inner.x": (1, 2)}}
Tell an instance from a class
Fill in is_dataclass_instance(obj), and keep its TypeIs return
annotation. It returns True for a dataclass instance, and False for a
dataclass class and for any other object. dataclasses.is_dataclass
returns True for a dataclass and for its instances, so also rule out
objects that are instances of type.
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. Keep both.
Compare two records
Fill in diff(a, b). It returns a dict[str, tuple[object, object]]
that maps each changed field's path to (old_value, new_value).
- If
type(a) is not type(b), raiseTypeError. That includes a base class and a subclass with the same fields. - Visit
dataclasses.fields(a)in declaration order. Skip a field whosecompareisFalse. - If both values are dataclass instances of the same class, call
diffon them. Put the field name and a dot in front of each path it returns. - Otherwise compare the values with
!=. If they differ, record the field name with the tuple(old_value, new_value).
Some cases follow from these steps:
diffdoes not recurse into nested values of different classes. Step 4 compares them, so they appear as one change if they differ.diffrecurses only into dataclass instances. A list or a dict counts as one value, even if it holds dataclasses.- Store the original values, not copies, and leave both inputs unchanged.
- Keep each change as a tuple. The tests tell a tuple from a list.
Write solve
solve(case) looks up a pair in CASES. Both values have the type
object, so check each one with is_dataclass_instance before you call
diff, and raise TypeError if either check fails. Return
{"error": "", "changes": changes} on success. If the checks or diff
raise TypeError, return {"error": "TypeError", "changes": {}}. Keep
the supplied fixtures, and do not hard-code their results.
| Case | Result |
|---|---|
identical |
No changes |
nested |
"inner.x": (1, 2) |
both |
"name": ("a", "b") and "inner.y": ("p", "q") |
compare_false |
No changes, because only note differs and it has compare=False |
class_arg |
TypeError, because Outer is a class, not an instance |
different_classes |
TypeError, because Outer and Other are different classes |
Type checking
Your code must pass mypy --strict without # type: ignore. Keep the
signature diff[T: "DataclassInstance"](a: T, b: T). It does not make
mypy check that a and b share a class, because mypy can choose
DataclassInstance itself as T. That is why diff checks
type(a) is type(b) when it runs.
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.