раздел 02 · подстраница 1

Как программа читает и пишет файл

Чтение и запись файла - базовый навык, на котором стоят и CSV, и JSON, и логи. Разберём по шагам и отдельно остановимся на том, что чаще всего ломается у новичков: на пути к файлу.

Где лежит файл: абсолютный и относительный путь

Когда вы пишете open("notes.txt"), программа ищет файл в текущей рабочей директории - то есть в той папке, откуда запущена программа. Это не то же самое, что папка, где лежит файл с кодом.

open("notes.txt")            # относительный путь: ищет в папке запуска
open("data/notes.txt")       # в подпапке data
open("/Users/me/notes.txt")  # абсолютный путь: всегда одно и то же место

Надёжный приём - строить путь относительно файла с кодом, а не относительно папки запуска:

from pathlib import Path

# папка, где лежит этот скрипт
base = Path(__file__).parent
notes_path = base / "notes.txt"   # рядом со скриптом, где бы его ни запустили

with open(notes_path, "r", encoding="utf-8") as f:
    print(f.read())

Четыре операции, которые покрывают всё

Прочитать целиком:

with open("notes.txt", "r", encoding="utf-8") as f:
    text = f.read()

Прочитать построчно (удобно для списков):

with open("notes.txt", "r", encoding="utf-8") as f:
    lines = [line.strip() for line in f]   # strip убирает перенос строки

Перезаписать целиком:

with open("notes.txt", "w", encoding="utf-8") as f:
    f.write("новое содержимое\n")

Дописать в конец:

with open("notes.txt", "a", encoding="utf-8") as f:
    f.write("ещё одна строка\n")

Почему with

Конструкция with open(...) as f: гарантирует, что файл закроется сам, даже если внутри случится ошибка. Без неё файл может остаться открытым, а изменения - не записаться на диск. Всегда используйте with - это стандарт.

Изменить одну запись в файле

В текстовом файле нельзя «отредактировать строку посередине» на месте. Шаблон такой: прочитали всё в память, изменили, записали обратно.

# заменить задачу "купить молоко" на "купить кефир"
with open("notes.txt", "r", encoding="utf-8") as f:
    lines = f.readlines()

lines = [l.replace("купить молоко", "купить кефир") for l in lines]

with open("notes.txt", "w", encoding="utf-8") as f:
    f.writelines(lines)

Именно эта неуклюжесть - «прочитай всё, поменяй, запиши всё обратно» - один из сигналов, что задача переросла файл и просится в базу данных, где можно изменить одну запись напрямую.

Тот же принцип в JavaScript

На сервере (Node.js) всё работает похоже:

const fs = require("fs");

fs.writeFileSync("notes.txt", "купить молоко\n");        // перезаписать
fs.appendFileSync("notes.txt", "позвонить маме\n");      // дописать
const text = fs.readFileSync("notes.txt", "utf-8");      // прочитать
console.log(text);

В браузере прямого доступа к файлам на диске нет - это сделано из соображений безопасности. Там для постоянного хранения используют localStorage или отправку данных на сервер.

Антипаттерны

  • Путать папку запуска и папку скрипта. Источник 90% ошибок «файл не найден». Стройте путь от __file__.
  • Читать огромный файл целиком в память. Для больших файлов читайте построчно, иначе программа упадёт по памяти.
  • Не обрабатывать отсутствие файла. Первый запуск - файла ещё нет. Оборачивайте чтение в try / except FileNotFoundError или проверяйте существование заранее.