Skip to content

Make hash_class deterministic across processes and memoize it - #42

Merged
EthanRosenthal merged 1 commit into
mainfrom
deterministic-memoized-hash-class
Sep 29, 2026
Merged

EthanRosenthal merged 1 commit into
mainfrom
deterministic-memoized-hash-class

Conversation

@EthanRosenthal

Copy link
Copy Markdown
Collaborator

Summary

hash_class gave different results in different processes. It used repr() on code constants and class variables, which breaks in three ways:

  1. Nested code objects (lambdas, inner functions, comprehensions) in co_consts: their repr includes a memory address, the absolute file path and the line number.
  2. Set constants (e.g. x in {"a", "b"} compiles to a frozenset): repr order depends on PYTHONHASHSEED.
  3. Class variables: sets reorder between runs, and default object reprs include at 0x....

Changes

  • Add _stable_repr:
    • Nested code objects are encoded recursively.
    • Sets are sorted.
    • Tuples, lists and dicts are handled element by element.
    • Classes become module.qualname.
    • Other values fall back to repr with memory addresses removed.
  • Move code-object encoding out of _extract_code_bytes into _code_bytes so nested code can reuse it.
  • Cache results per class and algorithm in a WeakKeyDictionary, so classes created at runtime can still be garbage collected.

Notes

  • If a class is mutated after it's been hashed, the cached hash won't update (noted in the docstring).
  • _confingy_class_hash values in saved fingys will differ from the ones produced before this change. The hash is only used for the viz diff's "changed" markers.

Test plan

  • New test: memoization computes the hash once per class and algorithm
  • New test: a change inside a lambda changes the hash
  • New test: the hash is identical in subprocesses run with different PYTHONHASHSEED values
  • make pytest, make mypy, make lint, make format-check pass

🤖 Generated with Claude Code

hash_class used repr() on code constants and class variables, which
embedded memory addresses and file paths (nested code objects, default
object reprs) and hash-seed-dependent ordering (set/frozenset). Replace
with a stable representation that recursively encodes nested code
objects, sorts sets, and strips addresses.

Also memoize results per class and algorithm using a WeakKeyDictionary.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@upwind-code-us

upwind-code-us Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Upwind Upwind Code Scan - ✅ Passed

0 newly introduced vulnerabilities · 0 resolved · 27 total in this PR vs main

Total breakdown: 🔴 1 Critical | 🔶 6 High | 🟡 17 Medium | 🟢 3 Low

View full analysis in Upwind Console

Scan completed in 19s

Scan history (1 scan)
Commit Scanned at New Resolved Net
d772408 < 2026-09-29 03:40 UTC 0 0 0

Last scanned: d772408 · 2026-09-29 03:40 UTC

@upwind-code-us

upwind-code-us Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Upwind Upwind IaC Scan - ✅ Passed

0 newly introduced misconfigurations · 0 resolved · 0 total in this PR vs the default branch

View full analysis in Upwind Console →

Scan completed in 6s

Scan history (2 scans)
Commit Scanned at New Resolved Net
d772408 2026-09-29 03:40 UTC — — —
d772408 < 2026-09-29 03:40 UTC 0 0 0

Last scanned: d772408 · 2026-09-29 03:40 UTC

@EthanRosenthal
EthanRosenthal merged commit efcffb2 into main Sep 29, 2026
8 checks passed
@EthanRosenthal
EthanRosenthal deleted the deterministic-memoized-hash-class branch September 29, 2026 14:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant