Справочник по Python · модуль 8 · исключения и файлы
Глава 8.12
Буфер и
буферизация
Вызов write() не означает, что данные оказались на диске. Они попадают в буфер — область памяти, откуда переносятся большими блоками. Это ускоряет работу в разы, но объясняет, почему незакрытый файл может остаться пустым.
- Категория
- Файлы
- Уровень
- Средний
- Связанные темы
- Запись в файл, with, производительность
- Зачем это знать
- Понимать, когда данные действительно попадают на диск
§1Что такое буфер
Буфер — временная область оперативной памяти, где данные накапливаются по пути между программой и устройством. Программа пишет в буфер, а на диск данные уходят потом, большими блоками.
- данные, записанные программой, но ещё не на диске
- свободное место в буфере
То же самое происходит и при чтении, только в обратную сторону: с диска считывается блок, а программа берёт из него по кусочку.
§2Зачем он нужен
| Задача | Что даёт буфер |
|---|---|
| меньше обращений к устройству | тысяча write() превращается в одну запись на диск |
| программа не ждёт | управление возвращается сразу, не дожидаясь диска |
| согласование скоростей | процессор быстрее диска и сети — буфер сглаживает разницу |
Разница в скорости огромна: обращение к оперативной памяти в тысячи раз быстрее обращения к диску. Без буферизации запись файла построчно означала бы отдельную операцию ввода-вывода на каждую строку.
Тот же принцип работает и вне файлов: буферизация видео при просмотре — это накопление кадров, чтобы воспроизведение не прерывалось при неровной скорости сети.
§3Данные не на диске
Главное практическое следствие: после write() файл на диске может быть ещё пуст.
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()
Метод сбрасывает буфер немедленно, не закрывая файл.
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Автоматический сброс
Если данных много, буфер переполняется и сбрасывается сам — вмешательства не требуется.
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 сброс происходит на каждом переводе строки.
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 отключает буфер полностью — данные уходят на устройство сразу.
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_SIZEwith open(...) as f:Итог главы
Данные при записи попадают не на диск, а в буфер — область памяти, откуда переносятся большими блоками. Это ускоряет работу, но означает, что до закрытия файла его содержимое на диске может отсутствовать. Сброс происходит при заполнении буфера, вызове flush(), закрытии файла и штатном завершении программы. Параметр buffering позволяет выбрать построчный режим или отключить буфер вовсе, но в обычных задачах настраивать его не нужно — достаточно with, который гарантирует сброс.