Справочник по Python · модуль 8 · исключения и файлы
Глава 8.7
Запись:
writelines()
Метод записывает целую последовательность строк за один вызов. Название обманчиво: «lines» здесь не значит «строки файла» — переводы строк writelines() не добавляет, ровно как и write().
- Категория
- Файлы
- Уровень
- Базовый
- Связанные темы
- Списки, генераторы, join()
- Зачем это знать
- Записывать коллекцию строк одним вызовом
§1Что делает метод
writelines(lines) принимает последовательность строк и записывает их подряд. По сути это цикл из главы 8.6, свёрнутый в один вызов.
for line in lines: то же самое, что
file.write(line) file.writelines(lines)
Обратите внимание на правую часть схемы: в цикле не было + "\n". Это и есть источник главного недоразумения.
§2Ловушка названия
Название подсказывает, что метод пишет «строки файла» — то есть разделяет их. Это не так.
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.
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(), который возвращал число символов, этот метод не возвращает ничего.
with open("demo.txt", "w", encoding="utf-8") as file: result = file.writelines(["a", "b"]) print(result)
None
Если нужно знать объём записанного, его считают заранее: sum(len(line) for line in lines).
§5Принимает любую последовательность
Аргументом может быть не только список — подойдёт любая итерируемая коллекция.
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().
with open("demo.txt", "w", encoding="utf-8") as file: file.writelines([1, 2])
TypeError: write() argument must be str, not int
Текст ошибки упоминает write(), хотя вызывали writelines(), — потому что метод внутри и вызывает write() для каждого элемента.
§7Генератор вместо списка
Раз подойдёт любая итерируемая коллекция, годится и генераторное выражение из главы 4.10 — тогда промежуточный список не создаётся.
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().
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()))
'первая\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)file.write("\n".join(lines) + "\n")file.writelines(f"{n}\n" for n in nums)NoneИтог главы
writelines() записывает последовательность строк одним вызовом и, вопреки названию, переводов строк не добавляет — так задумано ради симметрии с readlines(). Метод принимает любую итерируемую коллекцию, включая генератор, и потому подходит для больших объёмов. Возвращает он None. Для небольших списков нагляднее пара write() и join(), а если строки нужно ещё и обрабатывать — обычный цикл.