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

    Document and inbox representing local email stores with Python mailbox
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python mailbox: Local Email Stores

    Learn Python mailbox to read, create, and migrate Maildir, mbox, and MH stores with locking, flags, message parsing, and safe

    Ler mais

    Tempo de leitura: 5 minutos
    12/08/2026
    Text editor representing formatting with Python textwrap
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python textwrap: Format Text

    Learn Python textwrap to wrap, fill, shorten, indent, and dedent text with controlled width, whitespace, long words, and reusable settings.

    Ler mais

    Tempo de leitura: 4 minutos
    10/08/2026
    Folder and magnifying glass representing file-name filters with Python fnmatch
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python fnmatch: Filter File Names

    Learn Python fnmatch to filter file names with shell wildcards, control case sensitivity, exclude patterns, and distinguish glob from regex.

    Ler mais

    Tempo de leitura: 5 minutos
    10/08/2026
    Monitor with binary data representing compact numeric arrays in Python
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python array: Compact Numeric Data

    Learn Python array for compact numeric storage, binary files, byte order, memory views, and safe buffer interoperability.

    Ler mais

    Tempo de leitura: 6 minutos
    10/08/2026
    Color wheel representing RGB, HSV, and HLS conversions with Python colorsys
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python colorsys: RGB, HSV, and HLS

    Learn Python colorsys to convert colors between RGB, HSV, HLS, and YIQ, generate palettes, and avoid scale and precision mistakes.

    Ler mais

    Tempo de leitura: 5 minutos
    09/08/2026
    Configuration icon representing plist files with Python plistlib
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    plistlib: Read and Write Apple Plist Files

    Learn Python plistlib to read and write XML and binary plist files, validate data, handle dates, bytes, and UIDs safely.

    Ler mais

    Tempo de leitura: 6 minutos
    08/08/2026