Сравнение файлов в Python: пошаговый разбор способов
Как в Python проверить, одинаковы ли два файла: от быстрого filecmp до построчного diff и хеширования больших файлов без загрузки в память.

Задача звучит просто: есть два файла, надо понять, отличаются ли они. Но «отличаются» означает разное. Иногда нужно узнать факт «байт в байт одинаковы или нет» за минимальное время. Иногда — показать, какие строки изменились, как в git diff. А иногда файлы весят по несколько гигабайт, и загружать их целиком в память нельзя. Под каждый случай — свой инструмент из стандартной библиотеки. Разберём четыре подхода и цену каждого. Всё написано для Python 3.11+, но модули filecmp, difflib и hashlib в этой части стабильны с 3.x.
Подход 1: filecmp — быстрый ответ «да или нет»
Если вопрос звучит как «эти два файла идентичны?» и подробности не нужны, самый короткий путь — модуль filecmp из стандартной библиотеки.
import filecmp
# shallow=False заставляет сравнивать содержимое, а не только os.stat
одинаковы = filecmp.cmp("first.txt", "second.txt", shallow=False)
print(одинаковы) # True или False
Ключевой параметр здесь — shallow. По умолчанию он равен True, и тогда filecmp.cmp сравнивает только метаданные из os.stat: размер, время модификации, тип. Содержимое при этом не читается вовсе.
С shallow=True два файла с одинаковым размером и временем правки будут признаны равными, даже если внутри у них разные байты. Для проверки «реально ли совпадает содержимое» всегда ставьте shallow=False.
Как понять, что сработало: функция вернёт булево значение. Если один из файлов не существует, вы получите FileNotFoundError — это нормальное поведение, а не тихое False.
Сравнение целых каталогов
У того же модуля есть класс filecmp.dircmp — он сравнивает две директории и раскладывает файлы по спискам: совпавшие, различающиеся, присутствующие только слева или только справа.
import filecmp
сравнение = filecmp.dircmp("dir_a", "dir_b")
print("Только в dir_a:", сравнение.left_only)
print("Только в dir_b:", сравнение.right_only)
print("Различаются:", сравнение.diff_files)
Учтите: dircmp по умолчанию использует поверхностное сравнение (как cmp с shallow=True). Список diff_files строится по метаданным. Если нужна побайтовая точность по каталогу, придётся обходить common_files вручную и вызывать filecmp.cmp(..., shallow=False) для каждого.
Подход 2: difflib — показать, ЧТО именно изменилось
Когда важен не факт различия, а его содержание, нужен difflib. Он сравнивает последовательности строк и выдаёт результат в привычном формате diff.
import difflib
with open("old.txt", encoding="utf-8") as f:
строки_старые = f.readlines()
with open("new.txt", encoding="utf-8") as f:
строки_новые = f.readlines()
# unified_diff даёт компактный формат, как git diff
разница = difflib.unified_diff(
строки_старые,
строки_новые,
fromfile="old.txt",
tofile="new.txt",
lineterm="",
)
for строка in разница:
print(строка)
Строки, начинающиеся с -, есть только в старом файле, с + — только в новом, с пробела — общие. unified_diff возвращает генератор, поэтому если различий нет, цикл просто ничего не выведет.
Важная деталь: readlines() сохраняет символы перевода строки на концах. Если один файл заканчивается на \n, а второй нет, вы увидите различие в последней строке, даже когда текст визуально совпадает. Чтобы это убрать, читайте через f.read().splitlines() — тогда переводы строк отбрасываются.
HTML-таблица различий
Если результат нужно показать в браузере, у difflib есть HtmlDiff. Метод make_file(строки_a, строки_b) возвращает готовую HTML-страницу с двумя колонками и подсветкой. Удобно для отчётов, но на больших файлах генерирует тяжёлый HTML — не для гигабайтов.
Подход 3: хеширование больших файлов
С файлами на сотни мегабайт и больше побайтовое сравнение через filecmp.cmp(shallow=False) работает, но читает оба файла целиком. Если файлов много и сравнивать нужно каждый с каждым, эффективнее один раз посчитать хеш каждого и сравнивать короткие строки.
import hashlib
def хеш_файла(путь, размер_блока=65536):
# Читаем файл блоками, не загружая целиком в память
хешер = hashlib.sha256()
with open(путь, "rb") as f:
for блок in iter(lambda: f.read(размер_блока), b""):
хешер.update(блок)
return хешер.hexdigest()
if хеш_файла("big1.bin") == хеш_файла("big2.bin"):
print("Содержимое совпадает")
else:
print("Файлы различаются")
Конструкция iter(lambda: f.read(...), b"") читает файл блоками по 64 КБ, пока не встретит пустой байт-объект — признак конца файла. Пиковое потребление памяти при этом остаётся на уровне размера блока, а не размера файла.
В Python 3.11+ появился hashlib.file_digest() — он делает ровно то же самое, но за вас:
import hashlib
with open("big1.bin", "rb") as f:
цифра = hashlib.file_digest(f, "sha256").hexdigest()
file_digest доступен начиная с Python 3.11. На более старых версиях используйте ручной вариант с блоками выше.
Хеш не гарантирует «файлы одинаковы» с математической строгостью — теоретически возможна коллизия. Но для SHA-256 вероятность случайной коллизии настолько мала, что на практике ей пренебрегают. Не используйте для этой задачи MD5: он быстрее, но давно скомпрометирован.
Подход 4: побайтовое сравнение вручную
Когда нужно сравнить два больших файла между собой ровно один раз, хеширование избыточно: оно читает оба файла до конца, даже если они различаются во втором байте. Ручное сравнение блоками выходит из цикла на первом же расхождении.
def файлы_равны(путь_a, путь_b, размер_блока=65536):
with open(путь_a, "rb") as a, open(путь_b, "rb") as b:
while True:
блок_a = a.read(размер_блока)
блок_b = b.read(размер_блока)
if блок_a != блок_b:
return False
if not блок_a: # оба файла закончились одновременно
return True
По сути это упрощённая версия того, что делает filecmp.cmp под капотом. Писать руками стоит, только если вам нужна нестандартная логика — например, игнорировать первые N байт заголовка.
Что выбирать под задачу
| Задача | Инструмент | Цена |
|---|---|---|
| Быстро: одинаковы или нет | filecmp.cmp(shallow=False) | Читает оба файла целиком |
| По метаданным, без чтения | filecmp.cmp(shallow=True) | Может ошибиться при равных размере и дате |
| Показать различия по строкам | difflib.unified_diff | Только для текста, грузит файл в память |
| Много больших файлов, сравнение каждого с каждым | hashlib + кеш хешей | Читает файл целиком, но один раз |
| Два больших файла один раз | ручное сравнение блоками | Выходит на первом различии |
| Сравнить каталоги | filecmp.dircmp | По умолчанию поверхностное сравнение |
Где это ломается
- Кодировки.
difflibсравнивает строки, поэтому при открытии текста без указанияencodingвы рискуете получитьUnicodeDecodeErrorили неверное сравнение на файлах в разных кодировках. Открывайте текст явно сencoding="utf-8". - Переводы строк. Файл, сохранённый в Windows (
\r\n) и в Unix (\n), при бинарном сравнении и через хеш будет разным, хотя текст идентичен. Для текстовой сверки читайте черезsplitlines(). - Symlink и специальные файлы.
filecmp.cmpследует по символическим ссылкам. Если сравниваете ссылку и обычный файл, результат может удивить. - Кеш filecmp. Модуль кеширует результаты сравнения по
os.stat. Если файл изменили, ноmtimeостался прежним (редко, но бывает при быстрой перезаписи), вызовитеfilecmp.clear_cache().
Что настроить дальше
Если сравнение файлов — часть регулярного процесса, а не разовая проверка, посмотрите в сторону кеша хешей: храните пары «путь → sha256 → mtime» и пересчитывайте хеш, только когда изменилось время модификации. Это отдельная задача, здесь мы её не разворачиваем. Для сравнения структурированных данных (JSON, YAML) точнее сначала распарсить их в словари и сравнить объекты, а не текст — иначе перестановка ключей или лишний пробел дадут ложное «различаются». Начните с filecmp для быстрого ответа и переходите к остальным подходам, только когда упрётесь в его ограничения.
Разбор научной статьи: Сложный текст — понятным языком, с терминами
Спросить за 12 ₽Источники: filecmp — File and Directory Comparisons (Python docs), difflib — Helpers for computing deltas (Python docs)
Частые вопросы
У меня Python 3.9, а file_digest не работает — что делать?
Почему filecmp.cmp говорит, что файлы одинаковые, хотя они разные?
Как сравнить два файла, игнорируя разницу в переводах строк?
Что быстрее для больших файлов — хеш или побайтовое сравнение?
Можно ли использовать MD5 вместо SHA-256 для скорости?
Как показать различия между файлами так же, как git diff?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.