Экспорт Python вырос: SpooledTemporaryFile между памятью и диском

Экспорт строится постепенно, но библиотека отправки требует файловый объект с seek: она сначала определяет длину, затем перечитывает содержимое. Генератор строк этому интерфейсу не соответствует. BytesIO удобен для маленького результата, однако большой отчёт целиком занимает память процесса. Заранее точного размера нет.

Подготовить поток с seek

SpooledTemporaryFile начинает работу с буфером памяти и при превышении положительного max_size переносит содержимое в настоящий временный файл. Вызывающий код продолжает пользоваться тем же файловым интерфейсом. Это позволяет установить понятный порог для подготовительного этапа, не писать собственный перенос и не придумывать временное имя в общем каталоге.

Порог относится к накопленному содержимому потока. Он не ограничивает память всей операции: большие промежуточные строки, список всех записей и буфер библиотеки получателя учитываются отдельно. Поэтому производитель должен писать порции и не сохранять рядом полную копию выгрузки. Значение max_size=0 не задаёт нужный нам автоматический переход по размеру; укажите положительный предел явно.

Пример использует бинарный режим, чтобы измерение относилось к байтам, и порог восемь байтов для наглядности. После записи десяти байтов позиция находится в конце. Перед чтением мы возвращаем её в начало. Затем вызываем rollover явно: этот публичный метод полезен, когда следующему этапу нужен настоящий файл независимо от размера. Результат чтения после перехода должен остаться тем же.

Проверить байты после rollover

from tempfile import SpooledTemporaryFile

parts = [b"abcd", b"efgh", b"ij"]
with SpooledTemporaryFile(max_size=8, mode="w+b") as output:
    for part in parts:
        output.write(part)
    size = output.tell()
    assert size == 10
    output.seek(0)
    first = output.read()
    assert first == b"abcdefghij"
    output.rollover()
    output.seek(0)
    assert output.read() == first
    print(size, first.decode("ascii"))
assert output.closed
print("closed:", output.closed)

Вызов fileno тоже может заставить поток перейти на диск: файловый дескриптор нужен настоящему файлу. Поэтому перед выбором порога посмотрите, как получатель использует объект. Если библиотека сразу требует дескриптор, ожидание «все маленькие отчёты останутся в памяти» не выполнится. Сам интерфейс чтения и перемещения позиции от этого не меняется.

Задать срок жизни экспорта

Перед передачей внешнему коду выполните flush, если ему нужен доступ через файловый дескриптор, и установите нужную позицию. Передача потока с курсором в конце часто выглядит как успешная отправка пустого файла. В примере отдельная проверка размера до seek помогает увидеть, что длина и текущая позиция — связанные, но разные свойства.

Граница with должна охватывать фактическое использование получателем. Нельзя вернуть output из функции, завершив with, и ожидать дальнейшего чтения: ресурс уже закрыт. Если получатель читает асинхронно после возврата, передайте ему владение и определите, кто закроет поток при успехе и при ошибке. Эта граница важнее выбора временного каталога.

Для сервиса задайте лимит общего размера экспорта отдельно от порога перехода. Диск тоже конечен, а несколько параллельных отчётов умножают расход. Считайте реально записанные байты, прекращайте формирование при превышении бизнес-лимита и оставляйте закрытие потока в гарантированной области. Это даёт предсказуемый отказ вместо случайного заполнения хранилища.

Продолжить практику

Файловый интерфейс удобно закрепить в бесплатном курсе Python после основ, где есть файловые операции и их тестирование. Откройте программу и проверьте успешную запись, пустой результат и ошибку подготовки. Для следующего шага к менеджерам контекста и стандартной библиотеке есть «Python для профессионалов».

Продолжить на практике

  • Python для продвинутых: ООП, типизация и тестирование ↗

    Практический курс-мост от базового Python к разработке поддерживаемых приложений. Вы разберётесь со структурой пакетов, объектной моделью, современной типизацией и автоматическими тестами, а затем соедините эти инструменты в небольшом приложении с хранением данных и логированием.