2025, Oct 31 21:47
Как побайтно синхронизировать два бинарных файла в Python
Рабочий способ побайтной синхронизации бинарных файлов в Python: сравнение, правки в bytearray, запись и truncate. Почему запись в цикле часто не срабатывает.
Когда нужно выровнять содержимое двух двоичных файлов .SET побайтно, желание «залатать» различия прямо на месте понятно. В этом случае задача заключалась в том, чтобы скопировать отличающиеся байты из файла SET исправного GPS‑приёмника в файл приёмника, застрявшего в цикле загрузки. Первый вариант на Python без ошибок отработал в Windows 11, но целевой файл так и не изменился. Решение упиралось во время и способ записи данных.
Постановка задачи и неудачный пример
Идея была сравнить два файла и по ходу итерации сразу записывать отличия в целевой файл:
from itertools import zip_longest
counter = 0
with open(r"C:\Users\jimal\Desktop\APP2\mgnShell.set", "r+b") as bad_fp, open(r"E:\APP\mgnShell.set", "rb") as good_fp:
bad_blob = bad_fp.read()
bad_bytes = bytearray(bad_blob)
good_blob = good_fp.read()
good_bytes = bytearray(good_blob)
for b_bad, b_good in zip_longest(bad_bytes, good_bytes):
counter += 1
if b_bad != b_good:
bad_fp.seek(bad_bytes.index(b_bad))
if b_good == None:
bad_fp.write(bytes(0x00))
else:
bad_fp.write(bytes(b_good))
print(f"Done! Count was {counter}")Что пошло не так на самом деле
Суть проблемы была не в правах ОС и не в атрибутах папки. Загвоздка — в стратегии записи. Запись внутри цикла идёт вразрез с логикой операции. Сначала нужно собрать весь набор изменений, и лишь затем выполнить запись. Иными словами: обновляем bytearray в памяти, а по завершении цикла записываем готовый буфер на диск.
Есть и вторая часть решения: непосредственное изменение файла во время обхода данных усложняет контроль смещений и итогового размера. Сначала обновляем bytearray, потом единожды записываем — так файл получает связный и полный результат.
Рабочий подход
Исправленный сценарий читает оба файла, вычисляет отличия, обновляет целевой буфер в памяти и лишь после цикла записывает итоговый буфер в файл и обрезает его:
counter = 0
with open(r"C:\Users\jimal\Desktop\APP2\mgnShell.set", "rb+") as dst_fp, open(r"E:\APP\mgnShell.set", "rb") as src_fp:
dst_raw = dst_fp.read()
dst_arr = bytearray(dst_raw)
src_raw = src_fp.read()
src_arr = bytearray(src_raw)
for b_dst, b_src, idx in zip(dst_arr, src_arr, range(len(dst_arr))):
counter += 1
if b_dst != b_src:
dst_arr[idx] = b_src
data_out = dst_arr[0:len(src_arr)]
dst_fp.seek(0)
dst_fp.write(data_out)
dst_fp.truncate()
print(f"Done! Count was {counter}")Почему это важно
Правки бинарных файлов не прощают мелочей. Малейшая ошибка во времени записи или управлении смещениями приводит к отсутствию изменений или к частичной записи, которая тихо не достигает цели. Сбор всех модификаций в памяти с последующей единовременной фиксацией даёт чёткую границу: сначала считаем различия, затем сохраняем. Это соответствует простой ментальной модели — «прочитать, преобразовать, записать», — которую легче проверять и понимать.
Если цель — сделать файлы идентичными, порой самый простой путь — просто заменить один другим. Когда же нужно преобразование, зачастую проще и безопаснее открыть файлы на чтение, подготовить результат в памяти, а затем открыть на запись и сохранить финальный буфер. Если что‑то идёт не так, базовая отладка печатью — типы, длины, прогресс в цикле — помогает понять, что именно делает код на каждом шаге.
Итоги
Для побайтной синхронизации двух файлов вычисляйте все изменения в памяти, а в конце выполняйте одну запись. Перейдите в начало, запишите подготовленный буфер и обрежьте файл, чтобы его длина совпала с источником. Если нужна лишь идентичность содержимого, подумайте о прямой замене. А при диагностике делайте невидимое видимым: добавляйте печать значений, чтобы проверять предположения о данных и потоке управления.
Статья основана на вопросе с StackOverflow от jacob malu и ответе от jacob malu.