Python filecmp: Compare Files and Folders

Published on: August 3, 2026
Reading time: 5 minutes

Synchronizers, backup tools, deployment tests, and audit systems need to determine whether files or directory trees are equal. Reading every byte always works, but it can be expensive when thousands of items are involved. The Python filecmp module provides efficient comparison of individual files, batches, and directory trees, with a choice between metadata-based checks and content verification.

This guide explains cmp(), cmpfiles(), and dircmp, including the shallow option, the stat()-based cache, recursive comparison, symbolic links, concurrency, and when to add cryptographic hashes. It complements our articles about difflib, temporary files, reading files, PermissionError, and collections.

Basic comparison with cmp()

filecmp.cmp() accepts two paths and reports whether the files appear equal.

from filecmp import cmp

same = cmp("original.txt", "copy.txt")
print(same)

The default shallow=True permits files with the same os.stat() signature to be considered equal. That signature includes file type, size, and modification time.

Shallow comparison

A shallow comparison avoids reading content when metadata already matches.

same = cmp("a.bin", "b.bin", shallow=True)

This is fast for caches, build trees, and controlled directories, but it does not prove that every byte is equal. Two different files can have the same size and timestamp.

Content comparison

Use shallow=False to compare file bytes.

same = cmp("a.bin", "b.bin", shallow=False)

The official filecmp documentation notes that the function can still reuse a cached comparison result. Cache entries are invalidated when the associated stat() information changes.

Cache and fast rewrites

On filesystems with coarse timestamp resolution, a file can be rewritten quickly without an observable modification-time change. If size also remains the same, a previous result may be reused.

import filecmp

filecmp.clear_cache()
same = filecmp.cmp("a.txt", "b.txt", shallow=False)

clear_cache() is useful in tests and immediately after rapid writes. Closing and flushing files before comparison also reduces ambiguity.

Validate regular files

cmp() is intended for regular files. Validate paths first.

from pathlib import Path
from filecmp import cmp


def files_equal(a, b):
    pa = Path(a)
    pb = Path(b)
    if not pa.is_file() or not pb.is_file():
        raise ValueError("Both paths must be files")
    return cmp(pa, pb, shallow=False)

Symbolic links, devices, sockets, and special files require a defined policy. Decide whether to compare the link itself, its target, or reject it.

Compare many names with cmpfiles()

cmpfiles() compares same-named relative files in two directories.

from filecmp import cmpfiles

names = ["app.py", "config.toml", "README.md"]
match, mismatch, errors = cmpfiles(
    "version-a",
    "version-b",
    names,
    shallow=False,
)

print("Match:", match)
print("Different:", mismatch)
print("Errors:", errors)

The error list contains missing files, invalid entries, and unreadable paths. Do not treat an error as an ordinary difference; report its cause separately.

Missing files and permission failures

Comparisons can fail because of permissions, broken links, or concurrent changes.

from pathlib import Path

for name in errors:
    path_a = Path("version-a") / name
    path_b = Path("version-b") / name
    print(name, path_a.exists(), path_b.exists())

An audit tool should distinguish missing, unreadable, type-mismatched, and changed-during-read cases.

Directory analysis with dircmp

The dircmp class compares two directory trees and exposes lists plus nested comparisons.

from filecmp import dircmp

comparison = dircmp("project-a", "project-b")
print(comparison.left_only)
print(comparison.right_only)
print(comparison.common_files)
print(comparison.diff_files)

Attributes are lazily computed. Constructing the object does not necessarily traverse the whole tree.

Important dircmp attributes

  • left_list and right_list: entries on each side;
  • common: names present in both;
  • left_only and right_only: exclusive names;
  • common_dirs: shared subdirectories;
  • common_files: shared regular files;
  • common_funny: incompatible types or stat() failures;
  • same_files, diff_files, and funny_files: comparison results for common files.

Checking only diff_files misses files that exist on one side only.

Built-in reports

report() prints the current level. report_partial_closure() adds immediate common subdirectories. report_full_closure() traverses recursively.

comparison.report_full_closure()

These methods print to standard output. Web services, APIs, and tests should consume attributes and construct structured results instead.

Recursive traversal through subdirs

subdirs maps each common subdirectory name to another dircmp object.

def collect(comp, prefix=""):
    result = []
    for name in comp.left_only:
        result.append(("left_only", prefix + name))
    for name in comp.right_only:
        result.append(("right_only", prefix + name))
    for name in comp.diff_files:
        result.append(("different", prefix + name))
    for name, child in comp.subdirs.items():
        result.extend(collect(child, prefix + name + "/"))
    return result

Protect applications against huge trees, excessive depth, and concurrent modifications.

shallow in dircmp

Since Python 3.13, dircmp accepts a shallow argument.

comparison = dircmp("a", "b", shallow=False)

Use False when same_files and diff_files must represent actual content equality rather than metadata equivalence.

Ignoring names

The ignore parameter replaces the default list of ignored names.

comparison = dircmp(
    "a",
    "b",
    ignore=[".git", "__pycache__", ".venv", "node_modules"],
    shallow=False,
)

When supplying a list, include every name that should remain ignored. Do not hide relevant deployment artifacts merely to simplify the report.

The hide parameter

hide controls names omitted from report output, with defaults that include . and ...

In general, ignore removes entries from comparison, while hide affects presentation.

Explain text differences with difflib

filecmp reports that two files differ but does not explain how. Generate a textual diff afterward.

from difflib import unified_diff
from pathlib import Path

before = Path("a/config.ini").read_text(encoding="utf-8").splitlines(True)
after = Path("b/config.ini").read_text(encoding="utf-8").splitlines(True)

print("".join(unified_diff(before, after, fromfile="a", tofile="b")))

Line-based diffs are inappropriate for arbitrary binary formats; report size, digest, or use a format-specific tool.

When to use hashes

A byte comparison answers whether current local content matches. A persisted digest can verify integrity later or on another machine.

from hashlib import file_digest


def sha256(path):
    with open(path, "rb") as file:
        return file_digest(file, "sha256").hexdigest()

The official hashlib documentation provides SHA-256 and other algorithms. Avoid MD5 and SHA-1 for new tamper-resistance designs.

A hash still reads the file

Calculating a digest reads every byte and often costs more than a direct comparison that can stop at the first mismatch. Use hashes when a fingerprint must be stored, transmitted, signed, or compared repeatedly.

Race conditions

A file can change between stat(), reading, and report generation. A result describes an approximate moment, not a transactional snapshot.

Critical backup systems should use filesystem snapshots, appropriate locks, or temporary copies. Recheck metadata after comparison when consistency matters.

Link resolution depends on the underlying filesystem operations. A synchronizer must decide whether it preserves links or compares targets.

Use Path.is_symlink() and os.readlink() when link identity matters. Prevent followed links from escaping an authorized root.

Large files and trees

cmp(..., shallow=False) reads in blocks and does not require loading the whole file into memory. Comparing many large files still consumes substantial I/O.

Use metadata as an initial filter, limit concurrency to avoid saturating storage, and prioritize recently modified items. Cloud storage may provide server-side checksums.

Testing with temporary directories

from pathlib import Path
from tempfile import TemporaryDirectory
from filecmp import dircmp

with TemporaryDirectory() as a, TemporaryDirectory() as b:
    Path(a, "same.txt").write_text("ok", encoding="utf-8")
    Path(b, "same.txt").write_text("ok", encoding="utf-8")
    Path(a, "different.txt").write_text("A", encoding="utf-8")
    Path(b, "different.txt").write_text("B", encoding="utf-8")

    comp = dircmp(a, b, shallow=False)
    assert "same.txt" in comp.same_files
    assert "different.txt" in comp.diff_files

Also test exclusive entries, nested folders, permission errors, links, and fast rewrites.

Structured audit example

def audit(a, b):
    comp = dircmp(a, b, shallow=False)
    return {
        "only_a": comp.left_only,
        "only_b": comp.right_only,
        "same": comp.same_files,
        "different": comp.diff_files,
        "errors": comp.funny_files + comp.common_funny,
        "subdirectories": {
            name: audit(child.left, child.right)
            for name, child in comp.subdirs.items()
        },
    }

For untrusted trees, impose depth and item-count limits.

Common mistakes

  • Using shallow=True as cryptographic proof of equality.
  • Ignoring left_only and right_only.
  • Treating unreadable files as ordinary differences.
  • Forgetting the cache after rapid rewrites.
  • Comparing symbolic links without a policy.
  • Hashing everything without considering cost.
  • Assuming a tree remains unchanged during analysis.
  • Printing reports when structured data is required.

Best practices

  • Choose metadata or content comparison deliberately.
  • Use cmpfiles() for known batches.
  • Use dircmp for structure and recursion.
  • Classify missing, different, and error cases separately.
  • Use difflib to explain textual changes.
  • Use SHA-256 when a persistent fingerprint is needed.
  • Limit depth, file count, and concurrency.
  • Test rapid changes and cross-platform behavior.

Conclusion

The Python filecmp module provides a straightforward foundation for file and directory comparison. cmp() handles two files, cmpfiles() classifies a batch, and dircmp reveals structural and recursive differences between trees.

Reliability depends on the selected level. Metadata provides speed, content provides stronger confirmation, and hashes provide persistent fingerprints. By separating errors from differences, considering cache behavior, defining link policies, and handling concurrency, you can build deployment checkers, backup verifiers, and audits that produce clear results without reading more data than necessary.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Command terminal representing safe parsing with Python shlex
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python shlex: Parse Commands Safely

    Learn Python shlex to split command lines, handle quotes, use quote and join, build mini-languages, and reduce shell injection risks.

    Ler mais

    Tempo de leitura: 6 minutos
    02/08/2026
    Local database representing persistence with Python shelve
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python shelve: Simple Persistence

    Learn Python shelve for simple object persistence, mutable updates, pickle security, concurrency limits, and SQLite migration.

    Ler mais

    Tempo de leitura: 5 minutos
    01/08/2026
    Text documents representing version comparison with Python difflib
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python difflib: Compare Text and Files

    Learn Python difflib to compare text, measure similarity, create unified diffs, generate HTML reports, and suggest close names.

    Ler mais

    Tempo de leitura: 6 minutos
    01/08/2026
    Chart dashboard representing statistical data analysis in Python
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python statistics: Data Analysis Guide

    Learn Python statistics for mean, median, standard deviation, quantiles, correlation, regression, NormalDist, and KDE.

    Ler mais

    Tempo de leitura: 6 minutos
    31/07/2026
    Fraction charts representing exact rational numbers in Python
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python fractions: Exact Rational Numbers

    Learn Python fractions for exact rational arithmetic, automatic reduction, limit_denominator, formatting, and safe conversions.

    Ler mais

    Tempo de leitura: 5 minutos
    31/07/2026
    Calculator and documents representing precise Decimal calculations in Python
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python Decimal: Precise Calculations

    Learn Python Decimal for exact calculations, money, quantize, rounding modes, contexts, and validation without float errors.

    Ler mais

    Tempo de leitura: 6 minutos
    30/07/2026