Сообщить об ошибке.

Паттерны использования io.BytesIO() в Python

Содержание:

io.BytesIO - это "файл в памяти" для байтов. Очень удобный кирпичик.

Ниже набор реально полезных паттернов для io.BytesIO.

Временный буфер вместо файла на диске

Когда нужно что-то, что ожидает "файлоподобный" объект (read/write/seek), но писать на диск не хочется.

from io import BytesIO

buf = BytesIO()

buf.write(b"hello ")
buf.write(b"world")

buf.seek(0)          # как у файла
data = buf.read()    # b'hello world'

Типичный случай: библиотека принимает "файл", а вы можете дать ей BytesIO.

"Файл поверх bytes" - удобное чтение/парсинг

Когда данные уже есть в виде bytes, а хочется использовать API, которое работает с файлом (чтение по кускам, построчно, т.п.).

from io import BytesIO

data = b"line1\nline2\nline3\n"
f = BytesIO(data)

for line in f:
    print(line)  # b'line1\n' и т.д.

Паттерн: оборачиваем любой bytes / буфер в BytesIO и работаем как с файлом.

Сборка байтов по частям (builder)

Когда нужно сложить много кусочков без постоянных конкатенаций a + b + c:

from io import BytesIO

buf = BytesIO()
for i in range(3):
    buf.write(f"chunk{i};".encode("utf-8"))

result = buf.getvalue()  # b'chunk0;chunk1;chunk2;'

BytesIO под капотом делает это эффективно.

Адаптер в тестах: подмена бинарного файла

Отличный паттерн для юнит-тестов: вместо реального файла - BytesIO.

from io import BytesIO

def process_file(f):
    data = f.read()
    return data.upper()

def test_process_file():
    fake = BytesIO(b"abc")
    assert process_file(fake) == b"ABC"

Плюсы:

  • не надо трогать файловую систему;
  • легко проверить позиционирование (seek/tell), ошибки и т.д.

Связка BytesIO + zipfile, tarfile, PIL, requests и др.

Очень распространённый паттерн: архив / изображение / что-то ещё в памяти.

Архив zip полностью в памяти

import io
import zipfile

buf = io.BytesIO()

with zipfile.ZipFile(buf, mode="w", compression=zipfile.ZIP_DEFLATED) as z:
    z.writestr("hello.txt", "привет")
    z.writestr("data.bin", b"\x00\x01\x02")

zip_bytes = buf.getvalue()   # готовый .zip как bytes

Открыть существующий zip из bytes

zip_bytes = ...  # пришло по сети, из БД и т.п.
buf = io.BytesIO(zip_bytes)

with zipfile.ZipFile(buf, "r") as z:
    print(z.namelist())

Промежуточный буфер для сетевых протоколов

Например, вы прочитали большой кусок из сокета, а хотите его "распарсить" как поток.

from io import BytesIO

def parse_message(data: bytes):
    f = BytesIO(data)
    header = f.read(4)
    body_len = int.from_bytes(header, "big")
    body = f.read(body_len)
    return header, body

Паттерн: BytesIO как удобный "курсором по байтам".

Совместимость с API, который требует file-like

Много библиотек умеют принимать "файл" вместо пути:

  • PIL.Image.open(f)
  • pdfminer, mutagen, soundfile, wave и т.п.
from io import BytesIO
from PIL import Image  # пример

img_bytes = ...  # получили по сети
f = BytesIO(img_bytes)
img = Image.open(f)

Перемотка и переиспользование одного буфера

Иногда надо:

  1. записать что-то в буфер;
  2. перемотать назад;
  3. отдать куда-то как файл.
from io import BytesIO

buf = BytesIO()
buf.write(b"some data")
buf.seek(0)

def upload_file(fobj):
    # какая-то функция, которая читает из файла
    print(fobj.read())

upload_file(buf)  # b"some data"

Использование как "pipe" между двумя функциями

Одна функция пишет бинарные данные в файл, другая - читает. Можно соединить их через BytesIO, не трогая диск.

from io import BytesIO

def producer(f):
    f.write(b"from producer")

def consumer(f):
    f.seek(0)
    return f.read().upper()

buf = BytesIO()
producer(buf)
result = consumer(buf)
print(result)  # b"FROM PRODUCER"

Ограничения / особенности

  • Это всё в памяти => для очень больших объёмов лучше tempfile.TemporaryFile или обычный файл.
  • getvalue() возвращает копию (в CPython - иногда разделяет буфер, но логически считать копией).
  • Можно использовать buf.seek(0); buf.truncate() чтобы "очистить" и переиспользовать.
buf = BytesIO()
buf.write(b"old")
buf.seek(0)
buf.truncate()
buf.write(b"new")
print(buf.getvalue())  # b"new"