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

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

Глава 8.4

8.4

Файлы: основные
понятия

Файл — способ сохранить данные между запусками программы. Работа с ним начинается с файлового объекта, который создаёт функция open(), и определяется двумя решениями: каким режимом открыть и как получать доступ к данным — последовательно или напрямую.

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

§1Зачем программе файлы

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

Схемаданные копируются из оперативной памяти в файл
оперативная память · пока программа жива employee_name Синдия Чэндлер employee_id 7451Z pay_rate 18.65 write() Синдия Чэндлер 7451Z 18.65 файл на диске · переживёт завершение программы
  • одни и те же данные до и после записи

Обратите внимание, что именно копируется. В файл уходят значения, а имена переменных остаются в программе: файл не помнит, что 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")
      └───┬──┘   └┬┘   └──────┬──────┘
     имя файла  режим     кодировка
                          (для текстовых файлов)
main.pyсоздание файлового объекта
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.

main.pyодин файл, два взгляда
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Атрибуты файлового объекта

main.pyчто объект знает о себе
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().

main.pyработа с закрытым файлом
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.

main.pyфайла не существует
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() выглядит достаточной. Проблема в том, что между ними может произойти ошибка.

main.pyclose() не выполнится
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.name
Ошибки открытияexcept OSError as e:

Итог главы

Файл делает данные постоянными: они переживают завершение программы. Работа с ним идёт через файловый объект, который создаёт open() и который хранит путь, режим, кодировку и позицию. Режим определяет и права, и разрушительность операции: "w" стирает существующее содержимое. Кодировку для текстовых файлов указывают явно, иначе результат зависит от настроек машины. Открытый файл нужно закрывать, но close() не выполнится при исключении — надёжное решение даёт следующая глава.

Глава 8.4 · Файлы: основные понятия · Справочник по Python Автор: Макаров Максим Николаевич