Файлы и pathlib

⏱ 35 мин · 🧩 заданий: 5

open и with

from pathlib import Path
import tempfile

folder = Path(tempfile.mkdtemp())
path = folder / "notes.txt"

with open(path, "w", encoding="utf-8") as f:     # запись (файл создаётся или очищается)
    f.write("Первая строка\n")
    f.write("Вторая строка\n")
    print("третья строка", file=f)              # print тоже умеет писать в файл

with open(path, encoding="utf-8") as f:          # чтение (режим "r" по умолчанию)
    print(repr(f.read()))

with open(path, "a", encoding="utf-8") as f:     # дописать в конец
    f.writelines(["четвёртая\n", "пятая\n"])

with open(path, encoding="utf-8") as f:
    for number, line in enumerate(f, start=1):   # построчно, лениво
        print(number, line.rstrip("\n"))

with гарантирует, что файл закроется, а буфер запишется на диск — даже при исключении.

Режимы

Режим Смысл
r чтение (по умолчанию); файла нет → FileNotFoundError
w запись; файл очищается или создаётся
a дописывание в конец
x создать новый; если существует → FileExistsError
+ чтение и запись (r+, w+)
b бинарный режим (rb, wb) — байты вместо строк
t текстовый (по умолчанию)

Способы чтения

from pathlib import Path
import tempfile

path = Path(tempfile.mkdtemp()) / "data.txt"
path.write_text("один\nдва\nтри\n", encoding="utf-8")

with open(path, encoding="utf-8") as f:
    print(repr(f.readline()))          # одна строка
    print(f.readlines())               # остальные строки списком
with open(path, encoding="utf-8") as f:
    print(repr(f.read(6)))             # первые 6 символов
    print(f.tell())                    # позиция в байтах (кириллица — 2 байта на букву)
    f.seek(0)                          # вернуться в начало
    print(f.read().splitlines())
Способ Память Когда
for line in f одна строка большие файлы, логи — по умолчанию
f.read() весь файл маленькие файлы, конфиги
f.readlines() весь файл списком редко нужно
f.read(n) n символов/байт бинарные данные блоками

Кодировки

Файл на диске — это байты. Текстовый режим декодирует их в строку по кодировке:

from pathlib import Path
import tempfile

path = Path(tempfile.mkdtemp()) / "ru.txt"
path.write_text("Привет", encoding="utf-8")
print(path.read_bytes())                               # 12 байт: по 2 на букву
print(path.read_text(encoding="utf-8"))
try:
    path.read_text(encoding="ascii")
except UnicodeDecodeError as e:
    print("UnicodeDecodeError:", e.reason)
print(path.read_text(encoding="cp1251"))               # не та кодировка — «кракозябры»
print(path.read_text(encoding="ascii", errors="replace"))

Всегда указывайте encoding

Без encoding Python берёт системную кодировку: UTF-8 на Linux/macOS, но на Windows часто cp1251. Код «работает у меня», но ломается у коллеги. В Python 3.15 UTF-8 станет кодировкой по умолчанию, а пока — явно encoding="utf-8".

Бинарные файлы

from pathlib import Path
import tempfile, hashlib

path = Path(tempfile.mkdtemp()) / "blob.bin"
path.write_bytes(bytes(range(256)) * 1000)

sha = hashlib.sha256()
with open(path, "rb") as f:
    while chunk := f.read(64 * 1024):          # блоками по 64 КБ — для файлов любого размера
        sha.update(chunk)
print(path.stat().st_size, "байт, sha256 =", sha.hexdigest()[:16], "...")

pathlib — пути как объекты

pathlib.Path — современная замена os.path: пути склеиваются оператором /, работают на любой ОС.

from pathlib import Path, PurePosixPath, PureWindowsPath

p = Path("/home/anna/projects/shop/src/main.py")
print(p.name, p.stem, p.suffix)           # main.py main .py
print(p.parent, p.parent.parent.name)
print(p.parts[:3])
print(p.with_suffix(".txt").name, p.with_name("app.py").name)
print(Path("data") / "2026" / "report.csv")
print(p.relative_to("/home/anna"))
print(PureWindowsPath(r"C:\Users\Anna\file.txt").parts)
print(Path.home(), Path.cwd())
from pathlib import Path
import tempfile

root = Path(tempfile.mkdtemp())
(root / "src" / "utils").mkdir(parents=True, exist_ok=True)   # как mkdir -p
(root / "src" / "main.py").write_text("print('hi')\n", encoding="utf-8")
(root / "src" / "utils" / "text.py").write_text("", encoding="utf-8")
(root / "README.md").write_text("# Проект", encoding="utf-8")

print(sorted(p.name for p in root.iterdir()))                 # содержимое папки
print(sorted(p.name for p in root.glob("*.md")))              # шаблон в папке
print(sorted(str(p.relative_to(root)) for p in root.rglob("*.py")))   # рекурсивно
f = root / "src" / "main.py"
print(f.exists(), f.is_file(), f.is_dir(), f.stat().st_size)
f.rename(root / "src" / "app.py")
(root / "README.md").unlink()                                 # удалить файл
print(sorted(str(p.relative_to(root)) for p in root.rglob("*")))
os / os.path pathlib
os.path.join(a, b) Path(a) / b
os.path.exists(p) p.exists()
os.path.basename(p) p.name
os.path.splitext(p)[1] p.suffix
os.makedirs(p, exist_ok=True) p.mkdir(parents=True, exist_ok=True)
os.listdir(p) p.iterdir()
glob.glob("**/*.py", recursive=True) Path().rglob("*.py")
open(p).read() p.read_text(encoding=...)

Временные файлы

import tempfile
from pathlib import Path

with tempfile.TemporaryDirectory() as tmp:          # папка удалится после блока
    p = Path(tmp) / "report.csv"
    p.write_text("a,b\n1,2\n", encoding="utf-8")
    print(p.exists())
print(p.exists())

with tempfile.NamedTemporaryFile("w+", suffix=".txt", encoding="utf-8") as f:
    f.write("временные данные")
    f.seek(0)
    print(f.name.endswith(".txt"), f.read())

Атомарная запись

Если программа упадёт посреди write, файл останется наполовину записанным. Надёжный приём — записать во временный файл рядом и атомарно переименовать:

import os, tempfile, json
from pathlib import Path

def atomic_write(path: Path, text: str):
    fd, tmp = tempfile.mkstemp(dir=path.parent, suffix=".tmp")
    try:
        with os.fdopen(fd, "w", encoding="utf-8") as f:
            f.write(text)
            f.flush()
            os.fsync(f.fileno())            # гарантировать запись на диск
        os.replace(tmp, path)               # атомарная замена
    except BaseException:
        os.unlink(tmp)
        raise

target = Path(tempfile.mkdtemp()) / "state.json"
atomic_write(target, json.dumps({"version": 2}))
print(target.read_text(encoding="utf-8"))

Безопасность путей

from pathlib import Path

UPLOADS = Path("/srv/uploads").resolve()

def safe_path(user_filename: str) -> Path:
    candidate = (UPLOADS / user_filename).resolve()
    if not candidate.is_relative_to(UPLOADS):        # защита от ../../etc/passwd
        raise PermissionError(f"выход за пределы папки: {user_filename}")
    return candidate

print(safe_path("avatar.png"))
try:
    safe_path("../../etc/passwd")
except PermissionError as e:
    print("PermissionError:", e)

Никогда не подставляйте имя файла от пользователя в путь без проверки — это уязвимость path traversal.

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

«Как прочитать большой файл, который не помещается в память?» — Итерироваться по файловому объекту for line in f (построчно) или читать блоками f.read(chunk_size) в цикле; обрабатывать потоково через генераторы. Не использовать read() и readlines().

Итог

  • with open(path, mode, encoding="utf-8") — всегда с with и кодировкой.
  • Режимы r, w (очищает!), a, x, b; большие файлы — for line in f или блоками.
  • pathlib.Path: / для склейки, .name, .suffix, .parent, glob/rglob, read_text/write_text.
  • Временные файлы — tempfile, надёжная запись — временный файл + os.replace.
  • Проверяйте пути от пользователя (resolve + is_relative_to).

Практика решено 0 из 5

Вы не вошли — ответы проверяются, но прогресс не сохранится. Войти или зарегистрироваться.
Задание 1 Программа на Python

Напишите count_lines(path): вернуть количество непустых строк в текстовом файле (строки из одних пробелов тоже считаются пустыми). Читайте файл построчно, с encoding="utf-8". Тест сам создаст файл во временной папке.

Какие тесты проверяют код (2)
import tempfile, pathlib
p = pathlib.Path(tempfile.mkdtemp()) / "a.txt"
p.write_text("первая\n\n   \nвторая\nтретья", encoding="utf-8")
assert count_lines(p) == 3

import tempfile, pathlib
p = pathlib.Path(tempfile.mkdtemp()) / "empty.txt"
p.write_text("", encoding="utf-8")
assert count_lines(p) == 0

Задание 2 Программа на Python

Напишите find_by_ext(folder, ext): вернуть отсортированный список имён файлов (только имя, без папок) с расширением ext (например, ".py") во всей папке рекурсивно. Используйте pathlib.Path.rglob.

Какие тесты проверяют код (1)
import tempfile
from pathlib import Path
root = Path(tempfile.mkdtemp())
for name in ["a.py", "b.txt", "src/c.py", "src/deep/d.py", "src/e.md"]:
    (root / name).parent.mkdir(parents=True, exist_ok=True)
    (root / name).write_text("x")
assert find_by_ext(root, ".py") == ["a.py", "c.py", "d.py"]
assert find_by_ext(str(root), ".md") == ["e.md"]

Задание 3 Заполнить таблицу

Что делает режим открытия файла?

чтениезапись с очисткойдописывание в конецсоздать, ошибка если есть
"r"
"w"
"a"
"x"
Отметьте ячейку — «+», пустая — «−».
Задание 4 Один вариант ответа

Почему при открытии текстового файла стоит всегда указывать encoding="utf-8"?

Задание 5 Один вариант ответа

Как правильно прочитать файл размером 20 ГБ, чтобы найти строки с ошибками?

Войдите, чтобы отмечать пройденные уроки.