Skip to content

Every zip() now says what it expects, and two of them did not - #96

Merged
qmohsu merged 3 commits into
mainfrom
claude/zip-strict-audit
Aug 25, 2026
Merged

Every zip() now says what it expects, and two of them did not#96
qmohsu merged 3 commits into
mainfrom
claude/zip-strict-audit

Conversation

@qmohsu

@qmohsu qmohsu commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

zip() without strict= truncates to the shorter argument and says nothing.
BASELINE in tests/test_lint_debt_only_shrinks.py recorded 41 of them, and
the obvious move is ruff --fix --unsafe-fixes. It writes strict=False
the current behaviour, spelled out loud. That takes the count to zero, looks
exactly like the job being done, and preserves every defect the rule exists to
expose. Done by hand instead, one site at a time.

58 sites, not 41

The other 17 are in notebooks/, which is not in SOURCE_DIRS, so no ruff
guard here had ever read them — in the three notebooks a student is most likely
to open. Same shape as KNOWN_NON_EXAMPLE_CHAPTER_FILES: a file outside the
sweep's scope is a file nothing sweeps, and that scope is a tuple somebody wrote
once.

One real defect

ch5_fingerprinting/example_comparison.py built colors_box with five shades
against six methods, so Linear Regression's box kept matplotlib's default blue
among five pastels and read as deliberately highlighted. Nothing raised, nothing
left a range, no printed number moved — the sixth box simply never got its
colour, which only opening the PNG shows. comparison_all_methods.{svg,pdf,png}
regenerated, and all three reproduce byte-identically from the fixed code.

One site where strict=True is wrong

zip(scales, scales[1:]) is the consecutive-pairs idiom and depends on the
truncation, so strict raises on every call — confirmed by running it. It is
itertools.pairwise now. zip(xs, count()) and zip(xs, cycle(ys)) are the
same family; the tell is a second argument derived from the first, and a grep
for [1:] finds only the spelling you thought of.

The ratchet is tighter, not looser

B905 is deleted from BASELINE rather than set to 0, and the deletion
is the guard: test_no_rule_has_more_findings_than_it_did fails on any rule not
listed. Verified by adding one unguarded zip() and watching
B905: 1 findings, new, not by reading the assertion.

A green sweep only means something where the line runs

Measured with coverage: 41 of the 42 .py sites execute. 34 run under
the examples, the generators, and two paths a plain run never reaches — ch4's
--data branch and ch7's --animate callback, which between them hold two
sites nothing else touches. The other seven — six inside test bodies, one in
core/fingerprinting — needed the harness fixed before they could be seen:
[tool.coverage.run] omit in pyproject.toml excludes */tests/*, so a run
measuring test files reports every line in them as never executed, which looks
exactly like "this code never runs". With a scratch rcfile that drops the omit,
all seven execute.

The one remaining site is ch8's unobservable-mode print, behind
if n_unobservable > 0 — false for the fixes-aided configuration by design,
which is the point the example makes. Its twin in the odometry-only branch does
execute, and the pairing is equal-length by construction either way. The 17
notebook sites are covered by tests/docs/test_notebooks_run.py.

Test plan

  • ruff check --statistics over SOURCE_DIRS: B905 gone, every other rule
    unchanged against BASELINE
  • ruff check --select B905 .: zero repo-wide, notebooks included
  • Full suite: 3404 passed, 21 skipped (24m37s) — same counts as main
  • tests/docs/test_notebooks_run.py: 14 passed, all seven notebooks execute
    in a real kernel
  • All 14 example invocations and both generators exit 0 under strict=True
  • black --check: 299 files clean (six files reflowed by the inserted
    keyword, and every changed line in them mentions zip/strict)
  • Mutation: one unguarded zip() added → ratchet red; removed → green
  • ch5 figure regenerated: svg/pdf/png byte-identical to what is committed

🤖 Generated with Claude Code

qmohsu added 3 commits August 25, 2026 23:55
`zip()` without `strict=` truncates to the shorter argument and says nothing.
The ratchet recorded 41 of them and the obvious move is
`ruff --fix --unsafe-fixes`. It writes `strict=False` -- the current behaviour,
spelled out loud. That takes the count to zero, looks exactly like the job
being done, and preserves every defect the rule exists to expose. So this was
done by hand, one site at a time, and the count was the least interesting part.

**58 sites, not 41.** The other 17 are in `notebooks/`, which is not in
SOURCE_DIRS in tests/test_lint_debt_only_shrinks.py, so no ruff guard here had
ever read them -- in the three notebooks a student is most likely to open.
Same shape as KNOWN_NON_EXAMPLE_CHAPTER_FILES: a file outside the sweep's scope
is a file nothing sweeps, and that scope is a tuple somebody wrote once.

**One real defect.** ch5_fingerprinting/example_comparison.py built
`colors_box` with five shades against six methods, so Linear Regression's box
kept matplotlib's default blue among five pastels and read as deliberately
highlighted. Nothing raised, nothing left a range, no printed number moved --
the sixth box simply never got its colour, which only opening the PNG shows.
comparison_all_methods.{svg,pdf,png} regenerated; all three reproduce
byte-identically from the fixed code.

**One site where strict=True is wrong.** `zip(scales, scales[1:])` is the
consecutive-pairs idiom and depends on the truncation, so strict raises on
every call; it is `itertools.pairwise` now. `zip(xs, count())` and
`zip(xs, cycle(ys))` are the same family -- the tell is a second argument
derived from the first, and a grep for `[1:]` finds only the spelling you
thought of.

B905 is deleted from BASELINE rather than set to 0, and the deletion is the
guard: test_no_rule_has_more_findings_than_it_did fails on any rule not listed.
Verified by adding one unguarded zip() and watching `B905: 1 findings, new`.

Coverage says the sweep is not decorative: 34 of the 42 .py sites execute,
including two only reachable through ch4's `--data` branch and ch7's
`--animate` callback. Six sit inside test bodies, one is reached by
tests/core/fingerprinting/test_classification.py, and the last is ch8's
unobservable-mode print, which the fixes-aided configuration never enters and
which is equal-length by construction.

    ruff B905     41 in SOURCE_DIRS, 58 repo-wide  ->  0
    full suite    3404 passed / 21 skipped         ->  3404 passed / 21 skipped
    notebooks     7 execute in a real kernel       ->  still do, with strict=True
    black         299 files clean                  ->  299 files clean
The first pass measured coverage over the examples and the generators, which
reaches 34 of the 42 `.py` zip sites, and left the remaining eight argued for
rather than measured -- six inside test bodies, one in `core/fingerprinting`,
one unreachable in the shipped configuration.

Measuring the seven was worth doing and needed the harness fixed first.
`[tool.coverage.run] omit` in pyproject.toml excludes `*/tests/*` and
`*/test_*.py`, so a run measuring test files reports every line in them as
never executed -- which is exactly what "this code never runs" looks like, for
the third time in this audit. With a scratch rcfile that drops the omit, all
seven execute.

That leaves one site out of 42, ch8's unobservable-mode print, and it is the
one worth naming: `if n_unobservable > 0` is false for the fixes-aided
configuration by design, which is the point the example is making. Its twin in
the odometry-only branch does execute, and the pairing is equal-length by
construction either way.
Self-review of the previous commit. The comment explaining why deleting the
B905 entry tightens the ratchet pointed at "`appeared` above"; BASELINE is at
line 79 and `appeared` is at 133, inside the test. A comment that sends the
reader the wrong way through its own file is a small thing, and this one sits
on the sentence explaining the guard.
@qmohsu
qmohsu merged commit e36939a into main Aug 25, 2026
1 check passed
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