Модуль 8 · Исключения и файлы

Справочник по Python · модуль 8 · исключения и файлы

Глава 8.12

8.12

Буфер и
буферизация

Вызов write() не означает, что данные оказались на диске. Они попадают в буфер — область памяти, откуда переносятся большими блоками. Это ускоряет работу в разы, но объясняет, почему незакрытый файл может остаться пустым.

Категория
Файлы
Уровень
Средний
Связанные темы
Запись в файл, with, производительность
Зачем это знать
Понимать, когда данные действительно попадают на диск

§1Что такое буфер

Буфер — временная область оперативной памяти, где данные накапливаются по пути между программой и устройством. Программа пишет в буфер, а на диск данные уходят потом, большими блоками.

Схемапуть данных при записи
программа ваш код write() буфер · оперативная память блоками диск перенос происходит, когда буфер заполнен · flush() · close() · выход из with до этого момента файла на диске может не быть вовсе
  • данные, записанные программой, но ещё не на диске
  • свободное место в буфере

То же самое происходит и при чтении, только в обратную сторону: с диска считывается блок, а программа берёт из него по кусочку.

§2Зачем он нужен

ЗадачаЧто даёт буфер
меньше обращений к устройствутысяча write() превращается в одну запись на диск
программа не ждётуправление возвращается сразу, не дожидаясь диска
согласование скоростейпроцессор быстрее диска и сети — буфер сглаживает разницу

Разница в скорости огромна: обращение к оперативной памяти в тысячи раз быстрее обращения к диску. Без буферизации запись файла построчно означала бы отдельную операцию ввода-вывода на каждую строку.

Тот же принцип работает и вне файлов: буферизация видео при просмотре — это накопление кадров, чтобы воспроизведение не прерывалось при неровной скорости сети.

§3Данные не на диске

Главное практическое следствие: после write() файл на диске может быть ещё пуст.

main.pyпроверяем размер файла до закрытия
import os

file = open("demo.txt", "w", encoding="utf-8")
file.write("данные в буфере")

print(os.path.getsize("demo.txt"))

file.close()
print(os.path.getsize("demo.txt"))
Выводдо закрытия файл пуст
0
28

Двадцать восемь байтов — это пятнадцать символов в UTF-8. Пока файл не закрыт, их на диске нет.

§4Когда буфер сбрасывается

Сброс буфера — перенос накопленного на устройство. Происходит он в четырёх случаях.

СобытиеКомментарий
буфер заполнилсяразмер по умолчанию — 8192 байта
вызван close()в том числе автоматически через with
вызван flush()принудительно, в любой момент
программа завершилась штатноPython закрывает открытые файлы

Четвёртая строка объясняет, почему забытый close() обычно всё-таки не приводит к потере данных. «Обычно» — ключевое слово: при аварийном завершении процесса содержимое буфера пропадает.

§5Метод flush()

Метод сбрасывает буфер немедленно, не закрывая файл.

main.pyпринудительный сброс
import os

file = open("demo.txt", "w", encoding="utf-8")
file.write("данные в буфере")

print(os.path.getsize("demo.txt"))
file.flush()
print(os.path.getsize("demo.txt"))

file.close()
Выводданные на диске, файл ещё открыт
0
28

Применение одно: файл должен долго оставаться открытым, а данные — быть доступны немедленно. Типичный случай — журнал работающей программы, который читают в реальном времени.

§6Автоматический сброс

Если данных много, буфер переполняется и сбрасывается сам — вмешательства не требуется.

main.pyдесять тысяч символов
import os

file = open("demo.txt", "w", encoding="utf-8")
file.write("x" * 10000)

print(os.path.getsize("demo.txt"))
file.close()
Выводбуфер переполнился и сбросился
10000

Стандартный размер буфера — io.DEFAULT_BUFFER_SIZE, обычно 8192 байта. Десять тысяч символов его превысили, и часть данных ушла на диск ещё до закрытия.

§7Параметр buffering

Поведение буфера задаётся при открытии файла — аргументом buffering.

ЗначениеЧто означаетГде допустимо
-1 (по умолчанию)буфер обычного размеравезде
0без буферизациитолько двоичный режим
1построчная буферизациятолько текстовый режим
больше 1буфер указанного размера в байтахвезде

§8Построчная буферизация

При buffering=1 сброс происходит на каждом переводе строки.

main.pyперевод строки запускает сброс
import os

file = open("demo.txt", "w", encoding="utf-8", buffering=1)

file.write("строка без перевода")
print(os.path.getsize("demo.txt"))

file.write("\n")
print(os.path.getsize("demo.txt"))

file.close()
Выводдо перевода строки на диске пусто
0
37

Это разумный компромисс для журналов: каждая завершённая строка сразу оказывается на диске, но отдельная операция ввода-вывода на каждый write() не выполняется.

§9Без буферизации

Значение 0 отключает буфер полностью — данные уходят на устройство сразу.

main.pyтолько для двоичного режима
import os

file = open("demo.bin", "wb", buffering=0)
file.write(b"abc")

print(os.path.getsize("demo.bin"))
file.close()
Выводданные на диске немедленно
3

В текстовом режиме так нельзя: попытка даёт ValueError: can't have unbuffered text I/O. Причина — кодирование само по себе требует промежуточного накопления.

§10Буферизация при чтении

При чтении буфер работает так же, только незаметно. Вызов read(1) не означает обращения к диску за одним байтом: система считывает целый блок и отдаёт из него по запросу.

Схемаоткуда приходят данные
диск  ──блок 8192 байта──→  БУФЕР  ──read(1)──→  программа
                                   ──read(1)──→
                                   ──read(1)──→
                          следующее обращение к диску —
                          только когда буфер исчерпан

Поэтому чтение по одному символу в цикле работает не настолько медленно, как кажется. Но памяти это всё равно не экономит по сравнению с чтением строками, а кода требует больше.

§11Почему это важно для with

Теперь становится понятнее гарантия из главы 8.5. with закрывает файл, а закрытие сбрасывает буфер — значит, данные точно попадут на диск даже при исключении.

Сравнениечто произойдёт с данными
file = open(path, "w")     при исключении close() не выполнится,
file.write(data)           буфер не сброшен — данные потеряны
1 / 0
file.close()

with open(path, "w") as f: __exit__ вызовет close(),
    f.write(data)          буфер сброшен — данные на диске
    1 / 0

Именно поэтому в главе 8.5 записанное «данные» оказалось в файле, несмотря на деление на ноль. Без with файл остался бы пустым.

§12Когда вмешиваться

В подавляющем большинстве задач буферизацию настраивать не нужно — умолчание подобрано верно.

СитуацияЧто делать
обычная запись файланичего, with достаточно
журнал, который читают на ходуbuffering=1 или flush()
долгий процесс, важна каждая записьflush() после ключевых операций
обмен с внешним устройствомbuffering=0 в двоичном режиме
«файл пустой, хотя я писал»проверить, закрыт ли файл

Последняя строка — самый частый повод вспомнить эту главу. Ответ почти всегда один: файл ещё открыт, и данные лежат в буфере.

§13Частые ошибки

Что написаноЧто произойдётКак правильно
читать файл, пока он открыт на записьпрочитается пусто или частичнозакрыть файл или вызвать flush()
писать без with и без close()при аварии данные потеряютсяwith
flush() после каждой строкитеряется весь выигрыш буферизацииbuffering=1
buffering=0 в текстовом режимеValueErrorдвоичный режим или buffering=1
считать, что write() уже записал на дискданные пока только в памятипомнить о сбросе
вызывать flush() перед close()лишнее: закрытие сбрасывает самоубрать

§14Проверьте себя

Оказываются ли данные на диске сразу после write()?

Нет. Они попадают в буфер и переносятся на диск при заполнении буфера, при flush() или при закрытии файла.

Зачем нужна буферизация?

Она сокращает число обращений к медленному устройству, не заставляет программу ждать и сглаживает разницу в скоростях.

Чем flush() отличается от close()?

flush() сбрасывает буфер, оставляя файл открытым. close() делает то же самое и закрывает файл.

Что делает buffering=1?

Включает построчную буферизацию: сброс происходит на каждом переводе строки. Доступно только в текстовом режиме.

Почему buffering=0 нельзя в текстовом режиме?

Кодирование символов в байты само требует промежуточного накопления, поэтому полностью небуферизованный текстовый ввод-вывод невозможен.

Как связаны буфер и гарантия with?

with вызывает close() при любом выходе из блока, а закрытие сбрасывает буфер — поэтому данные попадают на диск даже при исключении.

§15Шпаргалка

Сбросить сейчасfile.flush()
Построчноopen(path, "w", buffering=1)
Без буфераopen(path, "wb", buffering=0)
Свой размерopen(path, "w", buffering=4096)
Размер по умолчаниюio.DEFAULT_BUFFER_SIZE
Надёжноwith open(...) as f:

Итог главы

Данные при записи попадают не на диск, а в буфер — область памяти, откуда переносятся большими блоками. Это ускоряет работу, но означает, что до закрытия файла его содержимое на диске может отсутствовать. Сброс происходит при заполнении буфера, вызове flush(), закрытии файла и штатном завершении программы. Параметр buffering позволяет выбрать построчный режим или отключить буфер вовсе, но в обычных задачах настраивать его не нужно — достаточно with, который гарантирует сброс.

Глава 8.12 · Буфер и буферизация · Справочник по Python Автор: Макаров Максим Николаевич