テクノロジー · 実用ガイド

dircmp.diff_filesが空のまま?二つの保持結果を更新し、共通ファイルをすべて比較

28項目のローカル検証で、clear_cache()、新しい既定dircmp、diff_filesだけの深い比較がそれぞれ見落とす理由を確認しました。

独自解説図。同じシグネチャの変更後も、モジュールキャッシュと旧dircmpリストが結果を保持する。
オリジナル解説イラスト:独自解説図:CPython 3.12.14の実測。モジュールキャッシュ消去は旧オブジェクトの計算済みリストを更新しない。 Watch at Ease

dircmp.diff_filesが空でも、二つのディレクトリのバイト列が等しい証明にはなりません。CPython 3.12.14で、4バイトのファイルをAAAAからBBBBへ変更し、サイズと更新時刻を意図的に維持すると、浅い比較も、以前キャッシュされた深い比較もTrueのままでした。

filecmpのモジュールキャッシュを消すと、新しい深い比較はFalseになりました。しかし既存dircmpの計算済み空リストは変わりません。そのdiff_filesを深く比較しても対象名はゼロです。二つの状態を区別し、明示した方針で現在の共通ファイルすべてを調べます。

1. 保持される結果は二種類ある

filecmp.cmpの比較シグネチャには種類、サイズ、更新時刻が含まれます。shallow=Trueでは同じシグネチャを読み取りなしで等しいと扱えます。shallow=Falseはこの近道を避けますが、パスとシグネチャが変わらなければ、文書にある比較キャッシュから過去の結果を得る可能性があります。

dircmpの属性は必要時に遅延計算され、そのオブジェクトに保持されます。実験では書き換え前にd0.diff_filesを評価しました。clear_cache()後の新しい深い比較がFalseでも、d0.diff_filesは元の空リストでした。モジュールキャッシュの消去はd0の再構築ではありません。

2. 制御した書き換えで再現する

所有する新規一時ディレクトリを使います。両側のreport.binへb"AAAA"を書き、固定の同じ更新時刻を設定します。clear_cache()、cmp(a,b,shallow=False)を実行し、d0=filecmp.dircmp(left,right)のd0.diff_filesを評価しました。最初の深い比較はTrue、リストは空でした。

右側だけをb"BBBB"に変え、os.utimeで元のmtimeを復元します。種類・サイズ・mtimeの比較シグネチャが同じで、実バイトが異なることを断言で確認しました。既定の浅い呼び出しも、キャッシュ済みの深い呼び出しもTrueでした。ファイルシステムの時刻分解能を測った実験ではありません。

3. オブジェクトを作り直しcommon_filesを比較

まず書き込み元を停止または隔離し、入力を安定させます。実測3.12ではfilecmp.clear_cache(); fresh=filecmp.dircmp(left,right,ignore=[],hide=[]); match,mismatch,errors=filecmp.cmpfiles(left,right,fresh.common_files,shallow=False)を使えます。空の除外リストは小さなテスト用方針で、運用全般への推奨ではありません。

変更後はmatchとerrorsが空、mismatchにreport.binが入りました。一方fresh.diff_filesをcmpfilesへ渡すと三つとも空でした。新しい既定オブジェクトも浅いため、誤って同じと扱ったファイルを除外済みです。common_filesを候補にし、left_only、right_only、common_funny、errorsも残してください。単一の成功フラグに縮めないことが大切です。

4. 新しい引数の前に実行版を確認

実際の3.12.14はfilecmp.dircmp(left,right,shallow=False)にTypeErrorを返しました。Python 3.13の文書ではこの引数は3.13追加とされています。3.13は実行していません。対応版の公開深比較オプションも、変化する入力を原子的なスナップショットにしたり、評価済み旧オブジェクトを更新したりする保証ではありません。

既定dircmpもメタデータの近道が使えなければ内容を読みます。最後の対照ではバイトが同じで更新時刻だけ異なる場合に浅い比較がTrueでした。浅い比較が常にメタデータだけを見るわけではなく、時刻差だけで内容差は証明できません。

5. エラーと名前の除外も結果に含める

missing.binはmismatchではなくcmpfilesのerrorsに入りました。extra.bin追加後、旧オブジェクトの計算済みright_onlyにはなく、新しいものにはありました。追加前に作成してもリスト未読のdircmpは、初回アクセスで追加を見ました。構築時刻はスナップショット境界ではありません。

一時テスト内の.gitという通常ファイルは既定ignoreで隠れ、ignore=[],hide=[]で現れました。比較対象名を先に定めます。内容差が空でも、名前、種類、走査範囲、権限が等しい証明にはなりません。今回は平坦な通常ファイルだけで、再帰、リンク追跡、特殊ファイル、並行変更は試していません。

6. 独立したバイト比較で検証を終える

実際に導入済みだったGNU cmp 3.10で、変更した二つのreport.binにcmp -sを実行すると終了値1でした。GNU上流マニュアルは0を同じ、1を異なる、2を実行上の問題と定義しています。新しいSHA-256も異なりました。エラーを一致や通常の内容差として扱わないでください。

28項目すべてがCPython 3.12.14/Linuxで通過しました。記録には実行版、プラットフォーム、GNU版、スクリプトSHAと結果があります。実運用では安定入力から名前の範囲、全共通ファイルの結果、未解決エラーを報告します。バイト一致は内容の観測であり、真正性、メタデータ一致、書き込み中の整合性には別の保証が必要です。

独自解説図。空のdiff_files再比較と全common_files深比較、GNU cmp終了値の対照。
オリジナル解説イラスト:独自解説図:全共通ファイルを調べてテストの差を検出。名前の範囲とエラーは別途確認する。 Watch at Ease