Чтение и запись файлов в Python 3
Чтение и запись файлов в Python сводятся к трем шагам: открыть файл функцией open(), вызвать методы чтения или записи и закрыть файл. Ниже разобраны все способы прочитать текст (целиком, по строкам, в список), записать и дописать данные, выбрать режим и кодировку, а также работа с байтами и позицией в файле.

Содержание статьи
Режимы открытия для чтения и записи
Что можно делать с файлом, определяет второй аргумент open(). Ошибка в режиме дает либо исключение, либо, что хуже, молча стертый файл. Подробно сама функция и закрытие файла разобраны в статье про открытие и закрытие файла, здесь собрана шпаргалка.
| Режим | Что делает | Если файла нет | Если файл есть |
|---|---|---|---|
| r | Чтение (по умолчанию) | FileNotFoundError | Читает с начала |
| w | Запись | Создает | Очищает содержимое |
| a | Дозапись в конец | Создает | Пишет после старых данных |
| x | Только создание | Создает | FileExistsError |
| r+ | Чтение и запись | FileNotFoundError | Не очищает |
К любому режиму можно добавить b (байты, например rb) или t (текст, используется по умолчанию). Все примеры ниже используют конструкцию with: она закрывает файл сама, даже если внутри блока произошла ошибка.
Чтение файла
Пусть рядом со скриптом лежит файл notes.txt из трех строк. Прочитать его можно четырьмя способами, и выбор зависит от размера файла и того, что нужно сделать с текстом.
Метод read()
read() без аргументов возвращает все содержимое одной строкой. С числовым аргументом читает не больше указанного количества символов (в бинарном режиме байтов), и следующий вызов продолжает с того места, где остановился предыдущий.
with open('notes.txt', encoding='utf-8') as f:
first = f.read(3) # первые 3 символа
rest = f.read() # все остальное
print(first)
print(rest)
Когда файл дочитан до конца, read() возвращает пустую строку, а не ошибку. Для файлов в сотни мегабайт read() без аргумента неудобен: весь текст окажется в памяти.
Методы readline() и readlines()
readline() читает одну строку вместе с символом переноса \n в конце. readlines() возвращает список всех строк, и в каждом элементе тоже остается перенос.
with open('notes.txt', encoding='utf-8') as f:
header = f.readline()
other = f.readlines()
print(repr(header)) # 'Первая строка\n'
print(other) # ['Вторая строка\n', 'Третья строка\n']
Если переносы не нужны, используйте f.read().splitlines(): метод вернет список строк уже без \n.
Перебор строк в цикле for
Файловый объект сам является итератором по строкам. Цикл for читает файл порциями, поэтому подходит для файлов любого размера, в том числе логов в несколько гигабайт. Приемы для очень больших файлов собраны в статье о чтении больших файлов.
with open('notes.txt', encoding='utf-8') as f:
for number, line in enumerate(f, start=1):
print(number, line.rstrip('\n'))
Запись в файл
Для записи файл открывают в режиме w, a или x. Попытка вызвать write() у файла, открытого на чтение, вызовет исключение io.UnsupportedOperation.
Методы write() и writelines()
write() принимает одну строку и возвращает количество записанных символов. Перенос строки он не добавляет, его нужно писать явно. writelines() принимает список или другой итерируемый объект строк и тоже не вставляет разделители.
lines = ['яблоко', 'груша', 'слива']
with open('fruits.txt', 'w', encoding='utf-8') as f:
f.write('Список фруктов\n')
f.writelines(item + '\n' for item in lines)
Записать можно только строку. Число или список сначала преобразуют: f.write(str(42)). Для словарей и списков удобнее формат JSON, о нем рассказано в материале про запись и чтение JSON-файлов.
Функция print() с параметром file
Если хочется, чтобы переносы и пробелы между значениями расставлялись сами, передайте файл в print(). Функция сама преобразует аргументы в строки.
with open('report.txt', 'w', encoding='utf-8') as f:
print('Итого:', 150, 'руб.', file=f)
print('Позиций:', 3, file=f)
Дозапись в конец файла
Режим w стирает старое содержимое сразу при открытии, даже если вы ничего не запишете. Чтобы добавить строки к существующему файлу, например к логу, используйте режим a.
from datetime import datetime
with open('app.log', 'a', encoding='utf-8') as log:
log.write(f'{datetime.now():%Y-%m-%d %H:%M} запуск\n')
Кодировка и переносы строк
Если не указать encoding, Python берет кодировку из настроек системы. В версиях до 3.15 на Windows это обычно cp1251, на Linux и macOS UTF-8, поэтому файл с русским текстом, записанный на одном компьютере, может прочитаться кракозябрами на другом. Указывайте encoding='utf-8' явно и при чтении, и при записи.
Если в файле встречаются битые байты, параметр errors задает реакцию: 'strict' (исключение, по умолчанию), 'ignore' (пропустить) или 'replace' (подставить знак замены). Для файлов, сохраненных в Блокноте с BOM, подходит кодировка 'utf-8-sig'.
В текстовом режиме Python при чтении превращает любые окончания строк (\r\n Windows и \n Linux) в \n, а при записи на Windows обратно в \r\n. Поведение меняет параметр newline. Например, модуль csv требует открывать файл с newline=''.
Бинарные файлы и позиция в файле
Картинки, архивы, PDF открывают в режимах rb и wb. Кодировку для них не указывают, а читаются и записываются объекты bytes. Так можно, например, скопировать файл блоками по 64 КБ:
with open('photo.jpg', 'rb') as src, open('copy.jpg', 'wb') as dst:
while chunk := src.read(65536):
dst.write(chunk)
Каждый открытый файл хранит текущую позицию. tell() возвращает ее, seek(offset, whence) перемещает: whence=0 считает от начала, 1 от текущего места, 2 от конца. В текстовом режиме смещения от текущего места и от конца допускаются только нулевые, поэтому произвольные переходы делают в бинарном режиме.
with open('photo.jpg', 'rb') as f:
signature = f.read(4)
f.seek(0, 2) # в конец файла
print(signature, f.tell(), 'байт')
Короткий путь через pathlib
Для небольших файлов есть методы объекта Path: они открывают, читают или пишут и закрывают файл одной строкой. Подробнее модуль описан в статье Pathlib вместо os.path.
from pathlib import Path
path = Path('hello.txt')
path.write_text('Привет!\n', encoding='utf-8')
print(path.read_text(encoding='utf-8'))
data = Path('photo.jpg').read_bytes()
Метод write_text() всегда перезаписывает файл. Для дозаписи или построчного чтения по-прежнему нужен open().
Методы файлового объекта
Сводная таблица методов, которые чаще всего нужны при чтении и записи. Полный список приведен в документации модуля io.
| Метод | Назначение |
|---|---|
| read(n=-1) | Прочитать n символов или все до конца |
| readline() | Прочитать одну строку |
| readlines() | Прочитать все строки в список |
| write(s) | Записать строку, вернуть число символов |
| writelines(lines) | Записать набор строк без разделителей |
| seek(offset, whence=0) | Переместить позицию |
| tell() | Вернуть текущую позицию |
| flush() | Сбросить буфер записи на диск |
| truncate(size=None) | Обрезать файл до размера size |
| close() | Закрыть файл |
Частые вопросы
Короткие ответы на вопросы, которые возникают при первой работе с файлами.
Почему после записи файл пустой?
Данные сначала копятся в буфере и попадают на диск при закрытии файла или вызове flush(). Если файл открыт без with и не закрыт, часть текста может не записаться. Вторая частая причина: файл повторно открыли в режиме w, и он очистился.
Как прочитать файл в список без символов переноса?
Используйте f.read().splitlines() или генератор [line.rstrip('\n') for line in f]. Второй вариант не держит в памяти лишнюю копию всего текста.
Как записать в файл число или список?
write() принимает только строки, поэтому число преобразуют через str(), список склеивают методом '\n'.join(), а сложные структуры сохраняют через модуль json.
Чем режим a отличается от w?
Режим w обнуляет файл при открытии и пишет с начала. Режим a сохраняет содержимое и добавляет новые данные в конец, причем запись всегда идет в конец, даже после вызова seek().
Что делать при ошибке UnicodeDecodeError?
Файл сохранен не в той кодировке, которую ожидает Python. Укажите правильную явно, например encoding='cp1251' для старых файлов Windows, или errors='replace', если пропуск нескольких символов допустим.