Файлы и with

Теория

Backend постоянно работает с файлами: выгрузки заказов для бухгалтерии, отчёты, логи, импорт товаров от поставщика. Файл открывают функцией open(путь, режим, encoding=...), получают файловый объект и через него читают или пишут. Режим определяет, что можно делать: "r" — читать, "w" — писать с нуля (старое содержимое стирается), "a" — дописывать в конец. Кодировку указывают всегда — encoding="utf-8": иначе Python возьмёт кодировку операционной системы, и на Windows русский текст превратится в «кракозябры».

Открытый файл нужно закрыть: пока он открыт, данные могут не дописаться на диск, а система ограничивает число открытых файлов. Надёжный способ — конструкция with open(...) as f:. Она закрывает файл автоматически, когда блок заканчивается, — даже если внутри произошла ошибка. Объекты, которые умеют так «прибирать за собой» в with, называют контекстными менеджерами; как написать свой, — в разделе ООП.

Наглядно

Таблица 1. Режимы открытия файла.

Режим Что делает Если файла нет Если файл есть
"r" чтение (по умолчанию) FileNotFoundError читаем с начала
"w" запись с нуля создаётся содержимое стирается
"a" дозапись в конец создаётся пишем после существующего
"x" только создание создаётся FileExistsError
"rb", "wb" то же для байтов (картинки, архивы) без encoding

Таблица 2. Способы читать текстовый файл.

Запись Результат Когда
f.read() весь текст одной строкой файл маленький
for line in f: строки по одной, с \n на конце файл любого размера — построчно
f.readlines() список всех строк с \n редко: загружает всё в память
f.write(text) записывает строку, \n сам не добавляет

На практике

Задача: выгрузить номера заказов в отчёт, вести журнал событий дозаписью и прочитать список артикулов от поставщика.

Шаг 1. Записываем файл

with открывает файл и закроет его после блока. write не добавляет перевод строки — его пишем сами:

python
order_ids = [1042, 1043, 1044]
with open("report.txt", "w", encoding="utf-8") as f:
    f.write("Заказы за день\n")
    for order_id in order_ids:
        f.write(f"{order_id}\n")
print(f.closed)

Результат:

вывод
True

После выхода из блока файл уже закрыт.

Шаг 2. Читаем целиком

Маленький файл можно прочитать одной строкой:

python
with open("report.txt", encoding="utf-8") as f:
    text = f.read()
print(text)

Результат:

вывод
Заказы за день
1042
1043
1044

Шаг 3. Читаем построчно

Файловый объект — итератор строк: большой файл читается по одной строке, не целиком. У каждой строки на конце \n:

python
with open("report.txt", encoding="utf-8") as f:
    header = next(f)
    ids = [int(line) for line in f]
print(repr(header), ids)

Результат:

вывод
'Заказы за день\n' [1042, 1043, 1044]

int() сам отбросил пробельные символы по краям, а в строке заголовка \n остался. Для текста строки обычно чистят: line.rstrip("\n").

Шаг 4. Дописываем, а не перезаписываем

Журнал событий растёт: режим "a" добавляет строки в конец. Для сравнения — что делает "w":

python
with open("events.log", "a", encoding="utf-8") as f:
    f.write("1042 оплачен\n")
with open("events.log", "a", encoding="utf-8") as f:
    f.write("1043 отменён\n")
with open("events.log", encoding="utf-8") as f:
    print(f.read(), end="")

with open("events.log", "w", encoding="utf-8") as f:
    f.write("1044 отправлен\n")
with open("events.log", encoding="utf-8") as f:
    print(f.read(), end="")

Результат:

вывод
1042 оплачен
1043 отменён
1044 отправлен

После "w" в журнале осталась одна строка: две прежние стёрты.

Шаг 5. Убеждаемся, что with закрывает файл при ошибке

Ошибка внутри блока не мешает закрыть файл:

python
try:
    with open("broken.txt", "w", encoding="utf-8") as f:
        f.write("начало выгрузки\n")
        raise ValueError("сбой в середине выгрузки")
except ValueError as e:
    print("ошибка:", e)
print("файл закрыт:", f.closed)

Результат:

вывод
ошибка: сбой в середине выгрузки
файл закрыт: True

Шаг 6. Обрабатываем отсутствующий файл

Файла от поставщика может не оказаться — это ожидаемая ситуация, а не повод падать:

python
def read_skus(path):
    try:
        with open(path, encoding="utf-8") as f:
            return [line.rstrip("\n") for line in f]
    except FileNotFoundError:
        return []

with open("supplier.txt", "w", encoding="utf-8") as f:
    f.write("KB-0142\nMS-0007\n")
print(read_skus("supplier.txt"), read_skus("missing.txt"))

Результат:

вывод
['KB-0142', 'MS-0007'] []

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

  • "w" вместо "a". Режим "w" стирает файл при открытии — журнал или накопленная выгрузка пропадают.
  • Забытый \n. write не переводит строку: без "\n" все записи слипаются в одну строку.
  • \n в прочитанных строках. for line in f отдаёт строки с переводом строки: "KB-0142\n" != "KB-0142". Чистите rstrip("\n").
  • Без encoding. Кодировка по умолчанию зависит от системы; файл, записанный на Linux, на Windows прочитается с «кракозябрами». Всегда encoding="utf-8".
  • open без with. Если до close() случится ошибка, файл останется открытым, а записанное — не сброшенным на диск.

Итог

Открытие

  • open(путь, режим, encoding="utf-8"); всегда через with — файл закроется сам, даже при ошибке.
  • Режимы: "r" — читать, "w" — писать с нуля (стирает!), "a" — дописывать, "x" — только создать; "b" — байты.

Чтение и запись

  • f.read() — всё сразу; for line in f — построчно, строки с \n.
  • f.write(text) — без автоматического \n.

Ошибки

  • Нет файла при чтении — FileNotFoundError; если это ожидаемо, его ловят.

Вопросы на собеседовании

Зачем открывать файлы через with?

with гарантирует закрытие файла при выходе из блока — и при нормальном завершении, и при исключении. Без этого файл может остаться открытым, а данные из буфера не записаться на диск. open() возвращает контекстный менеджер, у которого есть методы входа и выхода.

Как прочитать файл размером 10 ГБ?

Построчно: for line in f: — файловый объект сам является итератором строк и держит в памяти одну строку. f.read() и f.readlines() загрузят весь файл в память.

Чем режим "w" отличается от "a"?

"w" при открытии обрезает файл до нуля и пишет с начала, "a" открывает файл на дозапись в конец. Оба создают файл, если его нет.

Глубже

Что происходит в with по шагам и как сделать свой контекстный менеджер, — в разделе ООП, тема «Свои контекстные менеджеры».

Ошибка в тексте

Сообщить об ошибке могут зарегистрированные пользователи — так в сообщениях меньше спама.

Войти, чтобы сообщить