Implementation:Iterative Dvc Utils Diff
| Knowledge Sources | |
|---|---|
| Domains | Utilities, Data_Comparison |
| Last Updated | 2026-02-10 10:00 GMT |
Overview
Concrete tool for computing structured differences between old and new data dictionaries using XPath-based flattening, provided by the DVC library.
Description
The diff function in dvc/utils/diff.py is the main entry point for comparing two versioned data dictionaries. It is used primarily by the dvc diff, dvc params diff, and dvc metrics diff commands to produce human-readable difference reports between revisions.
The function accepts two dictionaries (old and new) keyed by file path. Each value is expected to be a dictionary with a "data" key containing the actual metrics or parameters. The function iterates over the union of all paths in both dictionaries and calls _diff for each path to compute per-path differences.
The internal _diff function first parses raw values via _parse, which handles None, native Python types (dict, list, int, float), and JSON-encoded strings. It then dispatches to _diff_dicts for dictionary values or _diff_vals for scalar values.
_diff_dicts is the core comparison engine. It flattens both old and new dictionaries into dot-separated XPath-style keys using flatten from dvc.utils.flatten (which delegates to the flatten_dict library with a dot reducer). Both flattened dictionaries are wrapped in defaultdict(lambda: None) so that missing keys return None rather than raising KeyError. The function then iterates over the union of all XPaths and calls _diff_vals for each pair of old and new values.
_diff_vals produces a result dictionary with old and new keys. When both values are numeric (int or float), it also computes a diff key containing the arithmetic difference (new - old). If with_unchanged is False (the default), identical old and new values produce an empty dictionary, effectively filtering them from the output. A special case handles single-element lists by unwrapping them before comparison.
The format_dict utility function recursively converts a nested dictionary for display purposes, converting list values to their string representations while leaving other types unchanged.
Usage
Import diff when you need to programmatically compare metrics or parameters between two DVC revisions. Import format_dict when you need to prepare diff results for display in tabular or JSON output formats.
Code Reference
Source Location
- Repository: DVC
- File:
dvc/utils/diff.py - Lines: 100 total
- Function: diff (L73-86)
- Function: format_dict (L89-99)
- Supporting module:
dvc/utils/flatten.py
Signatures
def diff(old, new, with_unchanged=False):
...
def format_dict(d):
...
Import
from dvc.utils.diff import diff, format_dict
I/O Contract
diff
Inputs:
| Name | Type | Required | Description |
|---|---|---|---|
| old | dict[str, dict] | Yes | A dictionary keyed by file path. Each value must contain a "data" key with the metrics/parameters data for the old revision. Missing paths are treated as empty.
|
| new | dict[str, dict] | Yes | A dictionary keyed by file path, structured identically to old, representing the new revision. |
| with_unchanged | bool | No | If True, include entries where old and new values are identical. Defaults to False (only changed entries are returned). |
Outputs:
| Name | Type | Description |
|---|---|---|
| return | dict[str, dict[str, dict]] | A nested dictionary indexed first by file path, then by dot-separated XPath. Each leaf value is a dictionary with keys: old (the previous value or None), new (the current value or None), and optionally diff (numeric difference when both values are numeric). Empty paths (scalar diffs) use an empty string as the XPath key. |
format_dict
Inputs:
| Name | Type | Required | Description |
|---|---|---|---|
| d | dict | Yes | A nested dictionary to format for display. |
Outputs:
| Name | Type | Description |
|---|---|---|
| return | dict | A new dictionary where list values are converted to their string representations and nested dicts are recursively formatted. Other types are preserved unchanged. |
Internal Functions
| Function | Description |
|---|---|
| _parse(raw) | Parses a raw value: returns None, native types (dict, list, int, float) as-is, attempts JSON deserialization for strings, and falls back to the raw string on decode failure. |
| _diff_vals(old, new, with_unchanged) | Compares two scalar values. Returns {"old": old, "new": new} with an optional "diff" key for numeric types. Returns empty dict if values are equal and with_unchanged is False. Unwraps single-element lists.
|
| _flatten(d) | Flattens a dictionary using dot-separated keys via flatten_dict. Returns a defaultdict with None as the default for missing keys. Returns "unable to parse" as default if input is not a dict.
|
| _diff_dicts(old_dict, new_dict, with_unchanged) | Flattens both dicts, computes the union of XPaths, and diffs each XPath pair using _diff_vals. |
| _diff(old_raw, new_raw, with_unchanged) | Top-level dispatch: parses raw values, delegates to _diff_dicts for dict types or _diff_vals for scalars. |
Usage Examples
Basic Metrics Diff
from dvc.utils.diff import diff
old = {
"metrics.json": {
"data": {"acc": 0.85, "loss": 0.45}
}
}
new = {
"metrics.json": {
"data": {"acc": 0.91, "loss": 0.32}
}
}
result = diff(old, new)
# result:
# {
# "metrics.json": {
# "acc": {"old": 0.85, "new": 0.91, "diff": 0.06},
# "loss": {"old": 0.45, "new": 0.32, "diff": -0.13}
# }
# }
Nested Parameters Diff
from dvc.utils.diff import diff
old = {
"params.yaml": {
"data": {"train": {"lr": 0.001, "epochs": 10}}
}
}
new = {
"params.yaml": {
"data": {"train": {"lr": 0.01, "epochs": 10, "batch_size": 32}}
}
}
result = diff(old, new)
# result:
# {
# "params.yaml": {
# "train.lr": {"old": 0.001, "new": 0.01, "diff": 0.009},
# "train.batch_size": {"old": None, "new": 32}
# }
# }
# Note: "train.epochs" is omitted because it is unchanged
Including Unchanged Values
from dvc.utils.diff import diff
old = {"metrics.json": {"data": {"acc": 0.9, "loss": 0.3}}}
new = {"metrics.json": {"data": {"acc": 0.9, "loss": 0.2}}}
result = diff(old, new, with_unchanged=True)
# result:
# {
# "metrics.json": {
# "acc": {"old": 0.9, "new": 0.9, "diff": 0.0},
# "loss": {"old": 0.3, "new": 0.2, "diff": -0.1}
# }
# }
Formatting Diff Results
from dvc.utils.diff import diff, format_dict
old = {"metrics.json": {"data": {"tags": ["v1", "v2"]}}}
new = {"metrics.json": {"data": {"tags": ["v1", "v2", "v3"]}}}
result = diff(old, new)
formatted = format_dict(result)
# Lists are converted to string representations for display