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

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

Глава 8.6

8.6

Запись в файл:
write()

Метод записывает строку в текущую позицию и не делает ничего сверх этого: не добавляет перевод строки, не преобразует числа, не спрашивает подтверждения. Два следствия — самые частые сюрпризы темы: всё слипается в одну строку, а режим "w" стирает файл при открытии.

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

§1Последовательная запись

Последовательный доступ из главы 8.4 означает, что данные пишутся подряд: каждая новая порция ложится в текущую позицию, а позиция сдвигается вперёд. Перескочить к произвольному месту нельзя — этим занимается seek() из главы 8.11.

Для записи файл открывают в подходящем режиме, и выбор режима здесь важнее, чем при чтении: ошибка стоит потерянных данных.

§2Режимы для записи

РежимЧто делаетСуществующее содержимое
"w"открывает для записиудаляется
"a"открывает для дозаписисохраняется, запись идёт в конец
"wb"двоичная записьудаляется
"w+"запись и чтениеудаляется

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

Схемачто режим делает с существующим файлом
файл до открытия файл после открытия "w" старый текст пусто содержимое стёрто "a" старый текст старый текст ▸ запись пойдёт в конец "x" старый текст FileExistsError файл не тронут "r" старый текст старый текст только чтение
  • содержимое уцелело
  • содержимое стёрто или файл не открыт

Решающая строка — первая. open(path, "w") стирает файл в момент открытия, ещё до первого write(): даже если программа упадёт следующей строкой, старых данных уже не будет. Это самая дорогая ошибка во всей теме файлов, и защита от неё — режим "x" из главы 8.13.

§3Метод write()

write(string) записывает строку в текущую позицию файла.

main.pyпростейшая запись
with open("demo.txt", "w", encoding="utf-8") as file:
    file.write("Привет")

print(repr(open("demo.txt", encoding="utf-8").read()))
Выводровно то, что записали
'Привет'

Никаких добавлений: ни пробела, ни перевода строки, ни кавычек. Что передали — то и оказалось в файле.

§4Что возвращает write()

Метод возвращает количество записанных символов — в отличие от методов изменения коллекций из модуля 3, которые возвращали None.

main.pyсохраняем результат
with open("demo.txt", "w", encoding="utf-8") as file:
    count = file.write("Привет")

print(count)
Выводшесть символов
6

Обратите внимание: символов, а не байтов. В UTF-8 эти шесть букв займут на диске двенадцать байтов — разница из главы 5.4. В двоичном режиме метод вернёт именно число байтов.

§5Перевод строки не добавляется

Самая частая неожиданность: два вызова подряд не создают двух строк.

main.pyбез символа перевода строки
with open("demo.txt", "w", encoding="utf-8") as file:
    file.write("первая")
    file.write("вторая")

print(repr(open("demo.txt", encoding="utf-8").read()))
Выводвсё слиплось
'перваявторая'
main.pyперевод строки добавлен вручную
with open("demo.txt", "w", encoding="utf-8") as file:
    file.write("первая\n")
    file.write("вторая\n")

print(repr(open("demo.txt", encoding="utf-8").read()))
Выводдве строки
'первая\nвторая\n'

Это отличает write() от print(), который перевод строки добавляет сам — параметр end из главы 1.4. Здесь такого умолчания нет.

§6Режим w стирает файл

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

Схемачто происходит с файлом
до запуска:   строка 1
              строка 2

open(path, "w")   ← файл стал пустым уже здесь

file.write("новое")

после:        новое
main.pyпрежние данные не сохранились
with open("demo.txt", "w", encoding="utf-8") as file:
    file.write("новое")

print(repr(open("demo.txt", encoding="utf-8").read()))
Выводот двух прежних строк не осталось следа
'новое'
При каждом запуске файл обнуляется

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

§7Режим a дописывает

Когда существующее содержимое нужно сохранить, берут режим "a" — от append.

main.pyдозапись в конец
with open("demo.txt", "a", encoding="utf-8") as file:
    file.write(" ещё")

print(repr(open("demo.txt", encoding="utf-8").read()))
Выводпрежнее «новое» на месте
'новое ещё'
ЗадачаРежим
создать отчёт заново"w"
вести журнал событий"a"
сохранить настройки"w"
копить измерения"a"

§8Запись нескольких строк

Чтобы записать список строк, write() вызывают в цикле, добавляя перевод строки.

main.pyтри строки из списка
lines = ["строка 1", "строка 2", "строка 3"]

with open("demo.txt", "w", encoding="utf-8") as file:
    for line in lines:
        file.write(line + "\n")

print(repr(open("demo.txt", encoding="utf-8").read()))
Выводкаждая строка со своим переводом
'строка 1\nстрока 2\nстрока 3\n'

Для этой задачи есть и специальный метод writelines() — ему посвящена глава 8.7. Забегая вперёд: перевод строки он тоже не добавляет.

§9Только строки

Аргумент должен быть строкой. Число метод не примет.

main.pyпопытка записать число
with open("demo.txt", "w", encoding="utf-8") as file:
    file.write(42)
Ошибкапреобразования не происходит
TypeError: write() argument must be str, not int

Это снова отличие от print(), который приводит к строке что угодно. Здесь преобразование нужно делать самому.

§10Запись чисел

main.pyтри способа записать число
value = 42

with open("demo.txt", "w", encoding="utf-8") as file:
    file.write(str(value) + "\n")
    file.write(f"значение: {value}\n")
    file.write("итого: {}\n".format(value))

print(open("demo.txt", encoding="utf-8").read())
Выводчисло стало текстом
42
значение: 42
итого: 42

Помните вывод главы 8.4: в текстовом файле лежат символы, а не числа. При чтении обратное преобразование делают через int() или float().

§11Двоичный режим

В режиме "wb" метод принимает не строку, а байты — тип bytes из главы 5.4.

main.pyстроку нужно закодировать
with open("demo.bin", "wb") as file:
    count = file.write("Привет".encode("utf-8"))

print(count)
print(open("demo.bin", "rb").read())
Выводдвенадцать байтов на шесть букв
12
b'\xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82'

Здесь видна разница из §4: символов было шесть, байтов записано двенадцать. Попытка передать обычную строку в двоичный файл даёт TypeError: a bytes-like object is required, not 'str'. Аргумент encoding в двоичном режиме не указывают — кодирование выполняется вручную.

§12Зачем нужен двоичный формат

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

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

§13Режим w+

Плюс к режиму означает «и чтение, и запись через один объект».

main.pyзаписали и тут же прочитали
with open("demo.txt", "w+", encoding="utf-8") as file:
    file.write("abc")
    file.seek(0)
    print(file.read())
Выводчтение из того же объекта
abc

Вызов seek(0) обязателен: после записи позиция стоит в конце файла, и чтение вернуло бы пустую строку. Работа с позицией разбирается в главе 8.11. Режим "w+", как и "w", стирает файл при открытии — для чтения существующего с возможностью правки берут "r+".

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

Что написаноЧто произойдётКак правильно
"w" вместо "a" для журналапри каждом запуске файл обнуляетсярежим "a"
два write() подряд без \nтекст слипнется в одну строкудобавлять "\n" самому
file.write(42)TypeError: нужна строкаfile.write(str(42))
file.write("текст") в режиме "wb"TypeError: нужны байты"текст".encode("utf-8")
считать возврат write() байтамив текстовом режиме это символыдля байтов открыть в "wb"
читать после записи без seek(0)вернётся пустая строкаперевести позицию в начало
открыть "w", чтобы «посмотреть» файлфайл будет очищенрежим "r"
писать без encodingна другой машине файл прочитается иначеencoding="utf-8"

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

Когда режим "w" удаляет содержимое файла?

В момент открытия — ещё до первого вызова write(). Даже если запись не состоится, файл уже пуст.

Добавляет ли write() перевод строки?

Нет. В отличие от print(), символ \n нужно дописывать самому.

Что возвращает write()?

Количество записанных символов в текстовом режиме и количество байтов — в двоичном.

Почему file.write(42) — ошибка?

Метод принимает только строку и ничего не преобразует. Нужен str(42) или f-строка.

Чем "a" отличается от "w"?

"a" сохраняет существующее содержимое и пишет в конец, "w" очищает файл при открытии.

Зачем seek(0) в режиме "w+"?

После записи позиция стоит в конце файла, и чтение вернуло бы пустую строку. seek(0) возвращает её в начало.

§16Шпаргалка

Записатьfile.write("текст\n")
Создать зановоopen(path, "w", encoding="utf-8")
Дописатьopen(path, "a", encoding="utf-8")
Числоfile.write(f"{value}\n")
Список строкfor line in lines: file.write(line + "\n")
Байтыfile.write(text.encode("utf-8"))

Итог главы

write() записывает строку в текущую позицию и возвращает число записанных символов. Он не добавляет перевод строки и не преобразует числа — обе привычки достались от print() и здесь не работают. Режим определяет судьбу существующего содержимого: "w" стирает файл уже при открытии, "a" дописывает в конец. В двоичном режиме метод принимает bytes, и строку приходится кодировать вручную.

Глава 8.6 · Запись в файл: write() · Справочник по Python Автор: Макаров Максим Николаевич