Этот ответ действителен для CPython 3.7. Другие реализации Python могут отличаться.
Функция open()
в текстовом режиме возвращает _io.TextIOWrapper()
._io.TextIOWrapper()
имеет внутренний буфер, называемый pending_bytes
с размером 8192 байта (это жестко запрограммированный ), и он также имеет дескриптор на _io.BufferedWriter()
для текстового режима w
или _io.BufferedRandom()
для текстового режима a
.Размер _io.BufferedWriter()
/ _io.BufferedRandom()
определяется аргументом buffering
в функции open()
.
При вызове _io.TextIOWrapper().write("some text")
текст будет добавлен во внутренний буфер pending_bytes
.После некоторых операций записи вы заполните буфер pending_bytes
, а затем он будет записан в буфер внутри _io.BufferedWriter()
.Если вы также заполните буфер внутри _io.BufferedWriter()
, он будет записан в целевой файл.
Когда вы откроете файл в двоичном режиме, вы получите непосредственно объект _io.BufferedWriter()
/ _io.BufferedRandom()
, инициализированный с размером буфераиз buffering
параметр.
Давайте рассмотрим несколько примеров.Я начну с более простого, использующего двоичный режим.
# Case 1
with open('test.log', 'wb', buffering=16) as f:
for _ in range(5):
f.write(b'a'*15)
strace output:
write(3, "aaaaaaaaaaaaaaa", 15) = 15
write(3, "aaaaaaaaaaaaaaa", 15) = 15
write(3, "aaaaaaaaaaaaaaa", 15) = 15
write(3, "aaaaaaaaaaaaaaa", 15) = 15
write(3, "aaaaaaaaaaaaaaa", 15) = 15
В первой итерации он заполняет буфер 15 байтами.Во второй итерации обнаруживается, что добавление еще 15 байтов приведет к переполнению буфера, поэтому он сначала очищает его (вызывает system write
), а затем сохраняет эти новые 15 байтов.В следующей итерации то же самое происходит снова.После последней итерации в буфере 15 B, которые записываются при закрытии файла (оставляя контекст with
).
Во втором случае я попытаюсь записать в буфер больше данных, чем размер буфера:
# Case 2
with open('test.log', 'wb', buffering=16) as f:
for _ in range(5):
f.write(b'a'*17)
strace output:
write(3, "aaaaaaaaaaaaaaaaa", 17) = 17
write(3, "aaaaaaaaaaaaaaaaa", 17) = 17
write(3, "aaaaaaaaaaaaaaaaa", 17) = 17
write(3, "aaaaaaaaaaaaaaaaa", 17) = 17
write(3, "aaaaaaaaaaaaaaaaa", 17) = 17
Здесь происходит то, что на первой итерации она пытается выполнить запись в буфер 17 B, но не может туда поместиться, поэтому она напрямую записывается в файли буфер остается пустым.Это применимо для каждой итерации.
Теперь давайте посмотрим на текстовый режим.
# Case 3
with open('test.log', 'w', buffering=16) as f:
for _ in range(5):
f.write('a'*8192)
strace output:
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 16384) = 16384
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 16384) = 16384
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 8192) = 8192
Сначала напомним, что pending_bytes
имеет размер 8192B. В первой итерации он записывает 8192 байта (из кода: 'a'*8192
) в pending_bytes
буфер.Во второй итерации он добавляет к pending_buffer
еще 8192 байта и обнаруживает, что он больше 8192 (размер буфера pending_bytes
), и записывает его в базовый _io.BufferedWriter()
.Буфер в _io.BufferedWriter()
имеет размер 16 B (параметр buffering
), поэтому он будет немедленно записывать в файл (так же, как в случае 2).Теперь pending_buffer
пуст и в третьей итерации он снова заполняется 8192 B. В четвертой итерации он добавляет еще одно переполнение буфера 8192 B pending_bytes
и снова записывается непосредственно в файл, как во второй итерации.В последней итерации он добавляет 8192 B в буфер pending_bytes
, который сбрасывается при закрытии файлов.
Последний пример содержит буферизацию, превышающую 8192 B. Также для лучшего объяснения я добавил еще 2 итерации.
# Case 4
with open('test.log', 'w', buffering=30000) as f:
for _ in range(7):
f.write('a'*8192)
прямой вывод:
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 16384) = 16384
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 16384) = 16384
write(3, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 24576) = 24576
Итерации:
- Добавление 8192 B в
pending_bytes
. - Добавление 8192 B в
pending_bytes
но он больше максимального размера, поэтому он записывается в базовый _io.BufferedWritter()
и остается там (pending_bytes
сейчас пуст). - Добавьте 8192 B в
pending_bytes
. - Add8192 B в
pending_bytes
, но это больше, чем максимальный размер, поэтому он пытается записать в базовый _io.BufferedWritter()
.Но это превысило бы максимальную емкость основной причины буфера 16384 + 16384 > 30000
(первые 16384 B все еще там после итерации 2), поэтому он сначала записывает старые 16384 B в файл, а затем помещает эти новые 16384 B (из pending_bytes
) в буфер,(Теперь снова буфер pending_bytes
пуст) - То же, что 3
- То же, что 4
- В настоящее время
pending_buffer
пусто, а _io.BufferedWritter()
содержит 16384 B. Вэта итерация заполняет pending_buffer
8192 B. И все.
Когда программа покидает секцию with
, она закрывает файл.Процесс закрытия выглядит следующим образом:
- Записывает 8192 B из
pending_buffer
в _io.BufferedWriter()
(возможно, из-за 8192 + 16384 < 30000
) - Записывает (
8192 + 16384
=) 24576 Bв файл. - Закройте дескриптор файла.
Кстати, в настоящее время я понятия не имею, почему существует pending_buffer
, когда его можно использовать для буферизации нижележащего буфера из _io.BufferedWritter()
. По-моему, он там, потому что он повышает производительность файлов, работающих в текстовом режиме.