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

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

Глава 8.7

8.7

Запись:
writelines()

Метод записывает целую последовательность строк за один вызов. Название обманчиво: «lines» здесь не значит «строки файла» — переводы строк writelines() не добавляет, ровно как и write().

Категория
Файлы
Уровень
Базовый
Связанные темы
Списки, генераторы, join()
Зачем это знать
Записывать коллекцию строк одним вызовом

§1Что делает метод

writelines(lines) принимает последовательность строк и записывает их подряд. По сути это цикл из главы 8.6, свёрнутый в один вызов.

Схемаэквивалентные записи
for line in lines:        то же самое, что
    file.write(line)      file.writelines(lines)

Обратите внимание на правую часть схемы: в цикле не было + "\n". Это и есть источник главного недоразумения.

§2Ловушка названия

Название подсказывает, что метод пишет «строки файла» — то есть разделяет их. Это не так.

main.pyтри элемента списка
lines = ["первая", "вторая", "третья"]

with open("demo.txt", "w", encoding="utf-8") as file:
    file.writelines(lines)

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

Метод задуман как обратный к readlines() из главы 8.10. Тот возвращает строки вместе с символами перевода, и если их сразу записать обратно через writelines(), файл восстановится точь-в-точь. Добавляй метод переводы сам — при таком круговом сценарии их стало бы вдвое больше.

§3Переводы строк вручную

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

main.pyперевод строки дописан заранее
lines = ["первая", "вторая", "третья"]

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

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

Заметьте: перевод строки оказался и после последнего элемента. Обычно это правильно — текстовые файлы принято завершать переводом строки.

§4Что возвращает

В отличие от write(), который возвращал число символов, этот метод не возвращает ничего.

main.pyрезультат вызова
with open("demo.txt", "w", encoding="utf-8") as file:
    result = file.writelines(["a", "b"])

print(result)
ВыводNone, как у методов изменения из модуля 3
None

Если нужно знать объём записанного, его считают заранее: sum(len(line) for line in lines).

§5Принимает любую последовательность

Аргументом может быть не только список — подойдёт любая итерируемая коллекция.

main.pyкортеж вместо списка
with open("demo.txt", "w", encoding="utf-8") as file:
    file.writelines(("a", "b"))

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

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

Элементы должны быть строками — ограничение то же, что у write().

main.pyв списке числа
with open("demo.txt", "w", encoding="utf-8") as file:
    file.writelines([1, 2])
Ошибкасообщение от внутреннего write()
TypeError: write() argument must be str, not int

Текст ошибки упоминает write(), хотя вызывали writelines(), — потому что метод внутри и вызывает write() для каждого элемента.

§7Генератор вместо списка

Раз подойдёт любая итерируемая коллекция, годится и генераторное выражение из главы 4.10 — тогда промежуточный список не создаётся.

main.pyчисла превращаются в строки на лету
with open("demo.txt", "w", encoding="utf-8") as file:
    file.writelines(str(n) + "\n" for n in range(3))

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

Для больших объёмов это существенно: миллион строк не придётся держать в памяти целиком.

§8writelines() или join()

Тот же результат даёт join() из главы 5.9 в паре с обычным write().

main.pyсклеиваем заранее
lines = ["первая", "вторая", "третья"]

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

print(repr(open("demo.txt", encoding="utf-8").read()))
Выводтот же результат, что в §3
'первая\nвторая\nтретья\n'
writelines()write() + join()
переводы строкк каждому элементумежду элементами
памятьможно писать по однойвся строка собирается целиком
завершающий \nесть сам собойнужно дописать
подходит длябольших объёмовнебольших списков

Разница в разделителе тонкая: join() ставит перевод между строками, поэтому в конце его приходится добавлять отдельно.

§9writelines() или цикл

Третий вариант — обычный цикл из главы 8.6. Он выигрывает, когда каждую строку нужно ещё и обработать.

Сравнениекогда цикл понятнее
for line in lines:                  можно фильтровать, считать,
    if line.strip():                 логировать — что угодно
        file.write(line + "\n")

file.writelines(lines)              только запись, ничего больше

Если в теле цикла ничего, кроме write(), — writelines() короче. Как только появляется условие или счётчик, цикл читается лучше.

§10Как выбрать

СитуацияЧто использовать
строки уже с переводами (из readlines())writelines(lines)
небольшой список без переводовwrite("\n".join(lines))
очень много строкwritelines() с генератором
нужна фильтрация или обработкацикл с write()
одна строкаwrite()

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

Что написаноЧто произойдётКак правильно
writelines(lines) для строк без \nвсё слипнется в одну строкудобавить перевод к каждому элементу
result = file.writelines(...)в переменной окажется Noneметод ничего не возвращает
writelines([1, 2])TypeError: нужны строкипреобразовать через str()
writelines("текст")запишется посимвольно: строка тоже коллекцияwrite("текст")
write("\n".join(lines)) без хвостафайл без завершающего переводадобавить + "\n"
строить список ради миллиона строклишний расход памятипередать генератор

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

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

Нет. Несмотря на название, элементы записываются подряд без разделителей.

Почему метод устроен именно так?

Он парный к readlines(), который возвращает строки вместе с переводами. При обратной записи добавлять их ещё раз было бы неверно.

Что вернёт writelines()?

None, в отличие от write(), который возвращает число записанных символов.

Что случится при writelines("текст")?

Строка сама является последовательностью символов, поэтому запишется посимвольно — результат тот же, но намерение неясно. Нужен write().

Чем передача генератора лучше списка?

Строки не хранятся в памяти целиком: они создаются и записываются по одной.

Когда лучше обычный цикл?

Когда кроме записи нужно что-то ещё — отфильтровать строки, посчитать их, что-то залогировать.

§13Шпаргалка

Записать списокfile.writelines(lines)
С переводамиfile.writelines(l + "\n" for l in lines)
Через joinfile.write("\n".join(lines) + "\n")
Числаfile.writelines(f"{n}\n" for n in nums)
ВозвращаетNone

Итог главы

writelines() записывает последовательность строк одним вызовом и, вопреки названию, переводов строк не добавляет — так задумано ради симметрии с readlines(). Метод принимает любую итерируемую коллекцию, включая генератор, и потому подходит для больших объёмов. Возвращает он None. Для небольших списков нагляднее пара write() и join(), а если строки нужно ещё и обрабатывать — обычный цикл.

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