Справочник по Python · модуль 8 · исключения и файлы
Глава 8.4
Файлы: основные
понятия
Файл — способ сохранить данные между запусками программы. Работа с ним начинается с файлового объекта, который создаёт функция open(), и определяется двумя решениями: каким режимом открыть и как получать доступ к данным — последовательно или напрямую.
- Категория
- Файлы
- Уровень
- Базовый
- Связанные темы
- Кодировки, исключения, строки
- Зачем это знать
- Сохранять данные, которые переживут завершение программы
§1Зачем программе файлы
Всё, что программа держала в переменных, исчезает при её завершении. Оперативная память освобождается, и данные пропадают. Если бы не файлы, пользователю пришлось бы вводить всю информацию заново при каждом запуске.
- одни и те же данные до и после записи
Обратите внимание, что именно копируется. В файл уходят значения, а имена переменных остаются в программе: файл не помнит, что 18.65 называлось pay_rate. Поэтому при чтении данные приходится разбирать обратно — по порядку, по разделителю или по заранее оговорённому формату.
| Программа | Что хранит в файлах |
|---|---|
| текстовый редактор | документы (.txt, .docx) |
| графический редактор | изображения (.png, .jpg) |
| электронные таблицы | данные и формулы (.csv, .xlsx) |
| игра | прогресс, настройки, ресурсы (.sav, .json) |
| браузер | кеш и куки: сеансы, предпочтения |
Файл делает данные постоянными — они переживают завершение программы, перезагрузку и переезд на другой компьютер.
§2Запись и чтение
Две операции описываются симметрично, и разница между ними — в направлении копирования.
ЗАПИСЬ переменная в ОЗУ ──→ файл на диске «файл вывода» ЧТЕНИЕ файл на диске ──→ переменная в ОЗУ «файл ввода»
Термины «файл вывода» и «файл ввода» описывают роль файла с точки зрения программы: в первый она помещает выходные данные, из второго извлекает входные. Один и тот же файл может быть тем и другим в разное время.
§3Текстовые и двоичные
Файлы делятся на два типа, и от этого зависит, как их открывать.
| Текстовый файл | Двоичный файл | |
|---|---|---|
| содержимое | символы, закодированные по схеме вроде UTF-8 | данные без преобразования в текст |
| можно открыть Блокнотом | да | нет |
| числа хранятся | как набор символов | в машинном представлении |
| примеры | .txt, .csv, .json, исходный код | изображения, аудио, архивы |
Важная деталь: даже если текстовый файл содержит число 18.65, внутри лежат пять символов, а не число. Это прямое следствие главы 5.4 — переход между текстом и байтами выполняет кодировка.
§4Последовательный и прямой доступ
Второе фундаментальное деление — способ добраться до нужного места.
| Доступ | Как работает | Аналогия |
|---|---|---|
| последовательный | от начала файла к концу, пропустить нельзя | магнитофонная кассета |
| прямой (произвольный) | переход сразу к нужной позиции | трек на диске или в плеере |
Чтобы прочитать данные из конца файла последовательно, придётся прочитать всё, что идёт перед ними. Прямой доступ позволяет перескочить к любому месту — этим занимаются методы seek() и tell() из главы 8.11. Большинство задач решается последовательным чтением, и с него начинаются следующие главы.
§5Файловый объект
Программа не работает с диском напрямую. Чтобы обратиться к файлу, она создаёт в оперативной памяти файловый объект — посредника, через которого идут все операции.
переменная ──→ файловый объект ──→ файл на диске
file в оперативной "data.txt"
памяти
Файловый объект хранит не содержимое файла, а сведения о нём: путь, режим, кодировку и текущую позицию чтения. Именно он предоставляет методы read(), write(), close() и остальные.
§6Функция open()
Файловый объект создаёт функция open(). Минимально ей нужно имя файла, обычно указывают также режим и кодировку.
open("data.txt", "w", encoding="utf-8")
└───┬──┘ └┬┘ └──────┬──────┘
имя файла режим кодировка
(для текстовых файлов)
file = open("demo.txt", "w", encoding="utf-8") print(type(file).__name__) file.close()
TextIOWrapper
Имя TextIOWrapper говорит о том, что это «обёртка» над потоком байтов, которая сама занимается кодированием. Для двоичного режима тип будет другим.
§7Режимы открытия
Второй аргумент задаёт, что именно программа собирается делать с файлом.
| Режим | Что делает | Если файла нет | Если файл есть |
|---|---|---|---|
"r" | чтение (по умолчанию) | FileNotFoundError | читает |
"w" | запись | создаёт | стирает содержимое |
"a" | дозапись в конец | создаёт | дописывает |
"x" | создание | создаёт | FileExistsError |
К режиму добавляется буква b для двоичного файла: "rb", "wb". Строчка "w" в таблице выделена не зря — это самая разрушительная операция в теме, и ей посвящена глава 8.6. Режим "x" подробно разбирается в главе 8.13.
§8Кодировка при открытии
Для текстового файла кодировку указывают явно — тем же аргументом encoding, о котором шла речь в главе 5.4.
with open("demo.txt", "rb") as binary: print(binary.read()) with open("demo.txt", "r", encoding="utf-8") as text: print(repr(text.read()))
b'\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xba\xd0\xb0\n' 'строка\n'
Двоичный режим отдаёт сырые байты, текстовый — уже декодированную строку. Без явного encoding Python возьмёт кодировку системы, и на другом компьютере файл прочитается иначе.
§9Атрибуты файлового объекта
file = open("demo.txt", "w", encoding="utf-8") print(file.name) print(file.mode) print(file.encoding) print(file.closed) file.close()
demo.txt w utf-8 False
Атрибут closed пригодится в отладке: он отвечает на вопрос «файл ещё открыт?». Сразу после open() он равен False.
§10Закрытие файла
Открытый файл занимает ресурс операционной системы, и его нужно освободить. Это делает метод close().
file = open("demo.txt", "w", encoding="utf-8") file.close() print(file.closed) file.write("ещё")
True ValueError: I/O operation on closed file.
Закрытие важно и по другой причине: записанные данные могут какое-то время оставаться в буфере и попасть на диск только при закрытии. Подробно про буфер — глава 8.12.
§11Ошибки при открытии
open() — операция, зависящая от внешнего мира, поэтому она может не удаться. Все такие ошибки наследуют от OSError из главы 8.3.
try: open("нет.txt") except FileNotFoundError as e: print(e)
[Errno 2] No such file or directory: 'нет.txt'
| Исключение | Причина |
|---|---|
FileNotFoundError | файла нет, а режим требует его наличия |
PermissionError | нет прав на чтение или запись |
IsADirectoryError | по указанному пути каталог, а не файл |
FileExistsError | режим "x", а файл уже существует |
Все четыре — потомки OSError, поэтому except OSError перехватит любую из них.
§12Почему close() ненадёжен
Пара open() и close() выглядит достаточной. Проблема в том, что между ними может произойти ошибка.
file = open("demo.txt", "w", encoding="utf-8") file.write("начало") result = 10 / 0 # исключение file.close() # сюда управление не дойдёт
Исключение прерывает выполнение, и файл остаётся открытым. Записанное может не попасть на диск, а ресурс не освободится. Формально это чинится через finally из главы 8.1, но в Python есть более короткое и надёжное решение — контекстный менеджер with, которому посвящена следующая глава. Именно поэтому в примерах выше уже встречалась запись with open(...) as file.
§13Частые ошибки
| Что написано | Что произойдёт | Как правильно |
|---|---|---|
open(path, "w") для чтения | файл будет очищен | open(path, "r") |
open(path) без encoding | на другой машине текст прочитается иначе | encoding="utf-8" |
работа с файлом после close() | ValueError: I/O operation on closed file | открыть заново |
забыть close() | данные могут не попасть на диск | использовать with |
close() после кода, который может упасть | при ошибке не выполнится | with или finally |
| читать двоичный файл в текстовом режиме | UnicodeDecodeError | режим "rb" |
| ждать от текстового файла хранения чисел | там символы, а не числа | преобразовывать через int() при чтении |
§14Проверьте себя
Что такое файловый объект?
Посредник в оперативной памяти между программой и файлом на диске. Он хранит путь, режим, кодировку и текущую позицию, а также предоставляет методы чтения и записи.
Чем текстовый файл отличается от двоичного?
В текстовом данные закодированы как символы и читаются любым редактором. В двоичном они хранятся в машинном представлении и предназначены только для программы.
Чем последовательный доступ отличается от прямого?
Последовательный требует прочитать всё, что идёт перед нужным местом. Прямой позволяет перейти к любой позиции сразу.
Что произойдёт при open(path, "w"), если файл существует?
Его содержимое будет стёрто. Для дозаписи нужен режим "a".
Зачем указывать encoding явно?
Без него берётся кодировка системы, и на другом компьютере тот же файл прочитается иначе — вплоть до испорченного текста.
Почему пары open() и close() недостаточно?
Исключение между ними прервёт выполнение, и close() не сработает: файл останется открытым, а данные могут не попасть на диск.
§15Шпаргалка
open(path, "r", encoding="utf-8")open(path, "w", encoding="utf-8")open(path, "a", encoding="utf-8")open(path, "rb")file.close()file.closedfile.nameexcept OSError as e:Итог главы
Файл делает данные постоянными: они переживают завершение программы. Работа с ним идёт через файловый объект, который создаёт open() и который хранит путь, режим, кодировку и позицию. Режим определяет и права, и разрушительность операции: "w" стирает существующее содержимое. Кодировку для текстовых файлов указывают явно, иначе результат зависит от настроек машины. Открытый файл нужно закрывать, но close() не выполнится при исключении — надёжное решение даёт следующая глава.