Python с нуля · Глава 8 из 10

Глава 8. Работа с файлами и исключения

Прогресс сохранится в этом браузере (войдите, чтобы синхронизировать).

Чтение и запись файлов

Для работы с файлом его нужно открыть функцией open(). Она возвращает файловый объект, который нужно закрыть методом .close():

f = open("hello.txt", "w", encoding="utf-8")
f.write("Привет, файл!\n")
f.close()

Но вручную закрывать файлы не принято — если между open и close случится ошибка, файл «зависнет» открытым. Поэтому всегда используют контекстный менеджер with:

with open("hello.txt", "w", encoding="utf-8") as f:
    f.write("Привет, файл!\n")
# после выхода из блока файл закроется автоматически, даже при ошибке

Режимы доступа

РежимНазначение
"r"чтение (по умолчанию). Файл должен существовать.
"w"запись. Файл создаётся или перезаписывается.
"a"дополнение (append). Данные дописываются в конец.
"x"создание. Ошибка, если файл уже есть.
"r+"чтение и запись.
"b"бинарный режим ("rb", "wb").

Всегда указывайте encoding="utf-8" для текстовых файлов. Иначе на разных ОС кодировка может отличаться и появятся «кракозябры» с русским текстом.

Чтение файла

# Целиком в одну строку
with open("hello.txt", encoding="utf-8") as f:
    content = f.read()
    print(content)

# Построчно
with open("hello.txt", encoding="utf-8") as f:
    for line in f:
        print(line.rstrip())   # rstrip убирает \n в конце

# Все строки в список
with open("hello.txt", encoding="utf-8") as f:
    lines = f.readlines()

Построчное чтение через for line in f — самое памято-эффективное: файл не загружается целиком в память. Это важно для больших файлов (логов на гигабайты).

Запись файла

lines = ["Первая строка", "Вторая строка", "Третья строка"]

with open("output.txt", "w", encoding="utf-8") as f:
    for line in lines:
        f.write(line + "\n")

# Или короче через writelines (но без добавления \n)
with open("output.txt", "w", encoding="utf-8") as f:
    f.writelines(line + "\n" for line in lines)

Модуль json

JSON — самый популярный формат для хранения и обмена данными. Модуль json входит в стандартную библиотеку:

import json

data = {
    "name": "Анна",
    "age": 28,
    "hobbies": ["чтение", "программирование"],
}

# Сериализация в строку
json_str = json.dumps(data, ensure_ascii=False, indent=2)
print(json_str)

# Запись в файл
with open("user.json", "w", encoding="utf-8") as f:
    json.dump(data, f, ensure_ascii=False, indent=2)

# Чтение из файла
with open("user.json", encoding="utf-8") as f:
    loaded = json.load(f)
    print(loaded["name"])   # Анна

ensure_ascii=False позволяет писать кириллицу напрямую, без \u0410.... indent=2 делает красивый отступ.

dumps / dump / loads / load — как запомнить

  • dumps — в string (строку).
  • dump — в файл.
  • loads — из строки.
  • load — из файла.

Исключения

Исключение — это ошибка во время выполнения. Если её не обработать, программа упадёт. Обрабатывают через try/except:

try:
    number = int(input("Введите число: "))
    print(10 / number)
except ValueError:
    print("Это не число!")
except ZeroDivisionError:
    print("Делить на ноль нельзя!")

Можно ловить несколько исключений в одном except как кортеж:

try:
    ...
except (ValueError, TypeError) as e:
    print(f"Ошибка ввода: {e}")

else и finally

  • else — выполняется, если исключения не было.
  • finally — выполняется всегда, было исключение или нет (закрытие ресурсов).
try:
    f = open("data.txt", encoding="utf-8")
except FileNotFoundError:
    print("Файл не найден")
else:
    content = f.read()
    f.close()
    print("Прочитано символов:", len(content))
finally:
    print("Этот блок выполнится в любом случае")

Распространённые исключения

ИсключениеКогда возникает
ValueErrorНеверное значение (int("abc"))
TypeErrorНеверный тип ("a" + 1)
KeyErrorНет такого ключа в словаре
IndexErrorИндекс за пределами списка
FileNotFoundErrorФайл не найден
ZeroDivisionErrorДеление на ноль
AttributeErrorНет такого атрибута/метода

Возбуждение исключений: raise

Свои ошибки можно «поднимать» через raise:

def set_age(age):
    if age < 0:
        raise ValueError("Возраст не может быть отрицательным")
    return age

try:
    set_age(-5)
except ValueError as e:
    print(e)   # Возраст не может быть отрицательным

Свои исключения

Для своих классов ошибок наследуются от Exception (мы подробнее разберём классы в следующей главе, а здесь — минимальный пример):

class EmptyCartError(Exception):
    '''Ошибка: попытка оформить пустую корзину.'''
    pass

def checkout(cart):
    if not cart:
        raise EmptyCartError("Корзина пуста")
    # ... оформление

Свои исключения делают обработку более выразительной: except EmptyCartError понятнее, чем общий except Exception.

Хорошие практики

  • Ловите конкретные исключения, а не «всё» через except Exception. Иначе спрячете настоящие баги.
  • Не глушите исключения пустым except: pass — хотя бы логируйте.
  • Узкая область try — оборачивайте только ту строку, которая может упасть.
  • Контекстный менеджер with для любых файлов и ресурсов.

Кейс из реального проекта: задачи в JSON

Типичный сценарий — приложение сохраняет состояние в файл и читает его при старте. Здесь же — обработка случая «файла ещё нет»:

import json
from pathlib import Path

TASKS_FILE = Path("tasks.json")

def load_tasks() -> list[str]:
    '''Загружает список задач из файла. Если файла нет — возвращает пустой список.'''
    try:
        with TASKS_FILE.open(encoding="utf-8") as f:
            return json.load(f)
    except FileNotFoundError:
        return []
    except json.JSONDecodeError:
        print("Файл повреждён, начнём с пустого списка")
        return []

def save_tasks(tasks: list[str]) -> None:
    '''Сохраняет список задач в файл.'''
    with TASKS_FILE.open("w", encoding="utf-8") as f:
        json.dump(tasks, f, ensure_ascii=False, indent=2)

# Использование
tasks = load_tasks()
tasks.append("Выучить Python")
save_tasks(tasks)

pathlib.Path — современный способ работы с путями (вместо os.path). Удобен тем, что работает одинаково на Windows и Unix.

Типичные ошибки

1. Открытие без with

f = open("x.txt")   # если дальше ошибка — файл не закроется
# правильно
with open("x.txt") as f:
    ...

2. Голый except

try:
    do_something()
except:             # глушит ВСЁ, включая KeyboardInterrupt
    pass

try:
    do_something()
except ValueError:  # ловим конкретное
    ...

3. Забыли encoding

# На Windows по умолчанию может быть cp1251 — кириллица сломается
with open("data.txt", "w") as f:    # плохо
    ...
with open("data.txt", "w", encoding="utf-8") as f:   # хорошо
    ...

Практика

  1. Запишите в файл 5 строк и прочитайте их обратно.
  2. Сохраните словарь с данными пользователя в JSON, затем загрузите и выведите.
  3. Напишите функцию safe_divide(a, b), которая возвращает None при делении на ноль.
  4. Прочитайте файл построчно и посчитайте количество строк и символов.
  5. Создайте своё исключение InvalidScoreError и возбудите его при значении вне диапазона 0-100.

Итог

Мы научились читать и писать файлы через with open(), работать с JSON через модуль json, обрабатывать ошибки через try/except/else/finally, возбуждать исключения через raise и создавать свои классы исключений. Эти навыки пригодятся в финальном проекте — там мы будем хранить задачи в JSON-файле. В следующей главе — модули и основы ООП.

Содержание серии (10)
{# Звук celebration (этап 4.3):