Watch at EaseStories. Answers. Perspective.

Technology · PRACTICAL GUIDE

dircmp.diff_files stays empty? Refresh both result stores and check every common file

A 28-check local experiment shows why clear_cache(), a fresh default dircmp and a deep recheck of diff_files can each leave different bytes undetected.

Original explanatory illustration showing cached deep True becoming False after clear_cache while an old dircmp list remains empty.
Original explanatory illustration: actual CPython 3.12.14 observations; clearing the module cache does not refresh an old computed list. Watch at Ease

An empty dircmp.diff_files is not proof that two directories contain identical bytes. In our CPython3.12.14 experiment, one four-byte file changed from AAAA to BBBB while its size and modification time were deliberately preserved. Both a shallow comparison and a previously cached deep comparison still returned True.

Clearing filecmp’s module cache made a fresh deep comparison return False. It did not update an existing dircmp object’s already computed empty result. Checking that empty diff_files list deeply compared zero names. The useful repair is to separate these states and compare all current common files under an explicit policy.

1. Distinguish the two kinds of retained result

filecmp.cmp uses file type, size and modification time in its comparison signatures. With shallow=True, equal signatures can be accepted without reading bytes. shallow=False avoids that shortcut, but the documented comparison cache can still supply a previous result when the paths and signatures have not changed.

dircmp adds a different lifetime: its attributes are computed lazily and then retained on that object. In our fixture, d0.diff_files was evaluated before the rewrite. After clear_cache() and a fresh deep False result, d0.diff_files still returned its original empty list. Clearing the module cache had not rebuilt d0.

2. Reproduce the failure with a controlled rewrite

Use fresh temporary directories you own. Write report.bin as b"AAAA" on both sides, assign both the same fixed modification time, clear_cache(), and evaluate cmp(a,b,shallow=False) plus d0=filecmp.dircmp(left,right); d0.diff_files. The initial deep result was True and the list was empty.

Rewrite only the right file as b"BBBB" and restore its original mtime with os.utime. We asserted that the compared type/size/mtime signatures were still equal and that the actual bytes differed. A default shallow call returned True; the previously cached deep call also returned True. This deliberately preserved signature is not a measurement of any filesystem’s timestamp resolution.

3. Refresh the object and compare common_files

First stop or isolate writers so the input set is stable. On the tested 3.12 runtime, call filecmp.clear_cache(); fresh=filecmp.dircmp(left,right,ignore=[],hide=[]); match,mismatch,errors=filecmp.cmpfiles(left,right,fresh.common_files,shallow=False). The explicit empty filtering lists are our tiny fixture policy, not a universal production recommendation.

For the changed fixture, match and errors were empty and mismatch contained report.bin. In contrast, passing fresh.diff_files into cmpfiles returned three empty lists: the fresh default object was still shallow and had already omitted the false-same file. Use common_files as the content candidate set; keep left_only, right_only, common_funny and errors visible instead of reducing the report to a single success flag.

4. Check the runtime before using the newer keyword

Our actual 3.12.14 interpreter raised TypeError for filecmp.dircmp(left,right,shallow=False). Python’s 3.13 reference documents that keyword as added in3.13. We did not execute3.13. On a compatible runtime it provides a public deep-comparison option, but it does not make mutable inputs an atomic snapshot or promise to refresh an already evaluated old object.

A default dircmp can read file contents when the metadata shortcut does not apply. Our final control used equal bytes and different modification times; shallow comparison returned True. Therefore shallow does not mean metadata-only in every case, and different timestamps do not by themselves establish different content.

5. Treat errors and name filtering as part of the result

Our missing.bin control appeared in cmpfiles’ errors list rather than mismatch. An added extra.bin was absent from an old object’s already computed right_only and present in a new object. A dircmp created before that addition, whose lists had not yet been accessed, saw it on first access: construction time was not a snapshot boundary.

A temporary ordinary file named .git was hidden by the default ignore policy and exposed with ignore=[],hide=[]. Decide what names your application intends to compare. Empty content differences do not establish matching names, matching types, complete traversal or identical permissions. This flat regular-file experiment did not test recursion, link traversal, special files or concurrent mutation.

6. Finish with an independent byte check and bounded conclusion

GNU cmp3.10 was actually installed for this run. cmp -s on the two changed report.bin files returned 1, independently confirming different bytes; the upstream GNU manual defines 0 as same,1 as different and2 as trouble. A fresh SHA-256 digest also differed. An error exit must not be reported as equality or as an ordinary content mismatch.

All28 assertions passed on CPython3.12.14/Linux. The saved report records exact runtime/platform, GNU version, script SHA and results. For a real audit, report the selected namespace, every common file’s comparison outcome and unresolved errors from a stable input set. Byte equality is a content observation; authenticity, metadata equality and consistency under writers require separate guarantees.

Original explanatory illustration contrasting zero names from diff_files with a report.bin mismatch from all common_files and GNU cmp exit1.
Original explanatory illustration: all common filenames reveal the fixture difference; namespace and error checks remain separate. Watch at Ease