Работа с бинарными файлами в Python
Бинарный файл в Python читается и записывается как последовательность байтов, без преобразования в текст и без учета кодировки. Из статьи вы узнаете, как открыть файл в режимах rb и wb, читать большие файлы по частям, перемещаться по ним через seek(), записывать числа в заданном формате модулем struct и сохранять объекты Python через pickle.

Содержание статьи
Чем бинарный файл отличается от текстового
Любой файл на диске состоит из байтов. Разница в том, как Python их отдает. В текстовом режиме байты декодируются в строки str по указанной кодировке, а переводы строк приводятся к \n. В бинарном режиме вы получаете объект bytes ровно в том виде, в котором данные лежат на диске.
Бинарный режим нужен для изображений, аудио, архивов, PDF, исполняемых файлов и любых собственных форматов. Если открыть картинку в текстовом режиме, чтение упадет с UnicodeDecodeError или испортит данные. Работа с обычным текстом описана в статье про текстовые файлы.
Режимы открытия файла
Бинарный режим включается буквой b, которая добавляется к основному режиму функции open(). Параметр encoding в бинарном режиме указывать нельзя, это вызовет ValueError.
| Режим | Действие | Если файла нет |
|---|---|---|
rb | Чтение | FileNotFoundError |
wb | Запись, старое содержимое стирается | Создается |
ab | Дозапись в конец | Создается |
xb | Запись только в новый файл | Создается, а если файл есть, FileExistsError |
r+b | Чтение и запись без очистки | FileNotFoundError |
Открывайте файлы через конструкцию with: она закроет файл даже при исключении. Общие правила открытия и закрытия разобраны в уроке открытие и закрытие файла.
Запись и чтение байтов
Метод write() принимает только байтовые объекты: bytes, bytearray или memoryview. Строку перед записью нужно закодировать методом encode(). Подробнее о байтовых строках в статье про функцию bytes.
data = bytes([0x48, 0x69, 0x00, 0xFF])
with open("sample.bin", "wb") as f:
f.write(data)
f.write("Привет".encode("utf-8"))
with open("sample.bin", "rb") as f:
content = f.read()
print(len(content)) # 16
print(content[:4]) # b'Hi\x00\xff'
print(content[4:].decode("utf-8")) # ПриветМетод read() без аргумента читает файл целиком, а read(n) возвращает не больше n байтов. Когда данные закончились, возвращается пустой объект b''. Индекс у bytes дает целое число от 0 до 255: content[3] вернет 255, а срез возвращает новый объект bytes.
Чтение большого файла по частям
Загружать в память видеофайл на несколько гигабайтов целиком нельзя. Файл читают блоками фиксированного размера в цикле. Оператор := позволяет прочитать блок и проверить его в одном выражении.
import hashlib
h = hashlib.sha256()
with open("video.mp4", "rb") as f:
while chunk := f.read(64 * 1024):
h.update(chunk)
print(h.hexdigest())Так считают контрольные суммы и копируют файлы с минимальным расходом памяти. Другие приемы описаны в статье про чтение больших файлов.
Перемещение по файлу через seek и tell
Метод tell() возвращает текущую позицию в байтах, а seek(offset, whence) перемещает ее. Второй аргумент задает точку отсчета: 0 от начала, 1 от текущей позиции, 2 от конца файла.
with open("photo.png", "rb") as f:
signature = f.read(8)
print(signature == b"\x89PNG\r\n\x1a\n") # True для PNG
f.seek(0, 2)
print("Размер:", f.tell(), "байт")Первые байты файла называют сигнатурой: по ним определяют настоящий формат, даже если расширение изменено.
Числа и структуры: модуль struct
Если нужно записать число не как текст, а в машинном представлении, используют модуль struct. Функция pack() превращает значения в байты по строке формата, unpack() выполняет обратное действие и всегда возвращает кортеж.
import struct
# < little-endian, i - int 4 байта, d - float 8 байт, 5s - 5 байт
record = struct.pack("<id5s", 42, 3.14, b"hello")
print(len(record)) # 17
with open("record.bin", "wb") as f:
f.write(record)
with open("record.bin", "rb") as f:
num, value, name = struct.unpack("<id5s", f.read(17))
print(num, value, name) # 42 3.14 b'hello'Префикс порядка байтов важен. Символ < задает little-endian и отключает выравнивание, > задает big-endian, принятый в сетевых протоколах. Без префикса используются нативные настройки платформы с выравниванием, и размер записи может отличаться на разных компьютерах. Узнать размер записи заранее помогает struct.calcsize("<id5s").
Сохранение объектов: модуль pickle
Модуль pickle сериализует почти любой объект Python: числа, строки, списки, словари, экземпляры классов. Функция dump() записывает объект в открытый бинарный файл, load() читает его обратно.
import pickle
users = [
["Tom", 28, True],
["Alice", 23, False],
["Bob", 34, False],
]
with open("users.dat", "wb") as f:
pickle.dump(users, f)
with open("users.dat", "rb") as f:
for name, age, married in pickle.load(f):
print(f"Имя: {name}, возраст: {age}, в браке: {married}")В один файл можно последовательно записать несколько объектов несколькими вызовами dump. Читать их нужно столько же раз и в том же порядке, иначе на лишнем вызове load возникнет EOFError. Функции dumps() и loads() работают так же, но с объектами bytes в памяти.
Загружайте pickle только из надежных источников: при десериализации может выполниться произвольный код. Для обмена данными с другими программами лучше подходят JSON или CSV. Детали формата приведены в документации модуля pickle.
Частые вопросы
Ответы на вопросы, которые возникают при работе с бинарными файлами.
Почему write() выдает TypeError: a bytes-like object is required?
Файл открыт в бинарном режиме, а в write передана строка. Закодируйте ее: f.write(text.encode("utf-8")), или откройте файл в текстовом режиме.
Как скопировать бинарный файл?
Проще всего вызвать shutil.copyfile(src, dst). Вручную копирование делается чтением блоков из одного файла в режиме rb и записью в другой в режиме wb.
Как вывести содержимое бинарного файла в шестнадцатеричном виде?
Прочитайте байты и вызовите метод hex(): f.read(16).hex(" ") вернет строку вида "89 50 4e 47 ...". Аргумент задает разделитель между байтами.
Чем pickle отличается от struct?
struct записывает числа и байты в жестко заданном формате, понятном программам на других языках. pickle сохраняет объекты Python целиком, включая вложенные структуры, но прочитать такой файл без Python нельзя.