v1.0.0

Python с нуля для ЕГЭ · Проверяем весь сценарий и наводим порядок в коде

Урок курсаPython 35 задачБесплатно

Проверяем весь сценарий и наводим порядок в коде

Проверим обычные и ошибочные сценарии, а затем приведём выросший код в порядок.

Теория

Как четыре урока сложились в одну программу

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

Разберём на примере
Что происходит с одним делом
Пользователь добавляет дело, закрывает программу, запускает её снова, отмечает дело и удаляет его.
  1. add_task создаёт словарь, а save_tasks записывает обновлённый список.
  2. После перезапуска load_tasks восстанавливает этот словарь из JSON.
  3. complete_task находит дело по id, меняет done и сохраняет результат.
  4. delete_task удаляет запись и записывает в файл уже пустой список.

Слово вместо номера не должно ломать программу

Сейчас случайное «два» вместо цифры 2 завершит программу на int(input()). Исправим это в одном месте. Функция чтения либо вернёт целое число, либо объяснит ошибку и отдаст None, после чего меню продолжит работу.

def read_task_id():    try:        return int(input("Номер дела: "))    except ValueError:        print("Номер должен быть целым числом")        return None def load_tasks():    try:        with open(FILE_NAME, "r", encoding="utf-8") as file:            data = json.load(file)            return data if isinstance(data, list) else []    except FileNotFoundError:        return []    except json.JSONDecodeError:        print("Файл повреждён. Начинаем с пустого списка")        return []

Здесь важно не смешать две ситуации. None от read_task_id означает, что номер вообще не удалось прочитать. А целое число может быть корректным, но не принадлежать ни одному делу. Для него покажем отдельное сообщение «Дело не найдено».

Проверьте себя
Чем ввод «abc» отличается от ввода номера 99, которого нет в списке?
Первый ввод нельзя преобразовать в целое число; второй корректен по формату, но поиск не находит запись.

Не путаем новый и повреждённый файл

Отсутствующий файл означает, что программу запустили впервые. Повреждённый JSON — уже проблема с данными, и молчать о ней нельзя. Сообщим, что файл не прочитан, и начнём с пустого списка. При этом не используем голый except: настоящие ошибки в коде по-прежнему должны показать traceback.

Проверяем программу как пользователь

Одна успешная команда ещё не доказывает, что приложение готово. Пройдём несколько обычных и проблемных сценариев подряд. Перед каждым шагом попробуйте предсказать, что останется в списке и в tasks.json, а затем сравните ожидание с результатом.

Как действовать
Шесть сценариев для финальной проверки
  1. Чистый старт. Без файла list показывает пустое состояние.
  2. Обычная работа. Добавьте два дела, измените одно и отметьте другое.
  3. Перезапуск. Проверьте текст, отметку и продолжение id.
  4. Ошибочный ввод. Введите слово и неизвестный номер; меню должно продолжить работу.
  5. Повреждённый JSON. Программа называет проблему и запускает пустой список.
  6. Чтение кода. Объясните одним предложением ответственность каждой функции.
import json FILE_NAME = "tasks.json" def load_tasks():    try:        with open(FILE_NAME, "r", encoding="utf-8") as file:            data = json.load(file)            return data if isinstance(data, list) else []    except FileNotFoundError:        return []    except json.JSONDecodeError:        print("Файл повреждён. Начинаем с пустого списка")        return [] def save_tasks(tasks):    with open(FILE_NAME, "w", encoding="utf-8") as file:        json.dump(tasks, file, ensure_ascii=False, indent=2) def read_task_id():    try:        return int(input("Номер дела: "))    except ValueError:        print("Номер должен быть целым числом")        return None tasks = load_tasks()next_id = max((task["id"] for task in tasks), default=0) + 1 def find_task(task_id):    for task in tasks:        if task["id"] == task_id:            return task    return None def choose_task():    task_id = read_task_id()    if task_id is None:        return None    task = find_task(task_id)    if task is None:        print("Дело не найдено")    return task def show_tasks():    if not tasks:        print("Список пока пуст")        return    for task in tasks:        mark = "x" if task["done"] else " "        print(f'[{mark}] {task["id"]}. {task["title"]}') def add_task():    global next_id    title = input("Новое дело: ").strip()    if not title:        print("Название не может быть пустым")        return    tasks.append({"id": next_id, "title": title, "done": False})    next_id += 1    save_tasks(tasks) def complete_task():    task = choose_task()    if task is None:        return    task["done"] = True    save_tasks(tasks) def edit_task():    task = choose_task()    if task is None:        return    title = input("Новый текст: ").strip()    if not title:        print("Название не изменено")        return    task["title"] = title    save_tasks(tasks) def delete_task():    task = choose_task()    if task is None:        return    tasks.remove(task)    save_tasks(tasks) while True:    command = input("Команда: ").strip().lower()    if command == "list":        show_tasks()    elif command == "add":        add_task()    elif command == "done":        complete_task()    elif command == "edit":        edit_task()    elif command == "delete":        delete_task()    elif command == "exit":        break    else:        print("Неизвестная команда")

Большая часть кода осталась прежней. Мы добавили только безопасное чтение номера и общую функцию выбора дела. Именно так удобнее развивать программу: не переписывать рабочие части, а аккуратно исправлять только те места, где обнаружилась проблема.

Наводим порядок без большой переписи

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

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

Проверьте себя
Что доказывает готовность итоговой программы лучше одного успешного запуска?
Набор повторяемых сценариев: чистый старт, обычные изменения, перезапуск, неверный ввод, неизвестный id и повреждённый файл.

Практика

Что означает повреждённый JSON

Какое исключение возникнет, если tasks.json существует, но JSON внутри не закончен?

Подсказка
Ищите ошибку декодирования в модуле json.
Решение

Повреждённый JSON вызывает json.JSONDecodeError; этот случай отличается от отсутствующего файла.

В каком порядке живут данные

Расположите этапы по порядку: загрузка, изменение, сохранение, новый запуск.

Подсказка
Сначала программа восстанавливает прежнее состояние, а новый запуск происходит уже после записи.
Решение

Программа загружает прежние дела, меняет и сохраняет список. При следующем запуске она читает уже обновлённые данные.

Почему нужны два сообщения об ошибке

Пользователь вводит abc, затем целое число 99, которого нет среди дел. Сколько разных сообщений об ошибке должна показать итоговая программа?

Подсказка
Неверный формат номера и неудачный поиск — две разные ситуации.
Решение

abc вызывает сообщение о формате целого числа, а 99 — сообщение «Дело не найдено».

Какое конкретное исключение нужно перехватить, чтобы обработать повреждённый JSON и не скрыть остальные ошибки?

Подсказка
Это исключение находится в модуле json.
Решение

except json.JSONDecodeError обрабатывает только повреждённый JSON. Остальные ошибки по-прежнему будут видны.

Проверяем номера после перезапуска

Добавьте два дела, удалите дело с id 1, перезапустите программу и добавьте ещё одно. Какой id получит новая запись?

Подсказка
Максимальный сохранённый id равен 2, а удалённый номер повторно не используется.
Решение

После двух добавлений следующий id равен 3. Удаление первой записи и перезапуск не уменьшают его.

Итог

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

Сохраните task_manager.py и придумайте для него одну собственную команду — например, показ только незавершённых дел. Сначала запишите, что должен ввести и увидеть пользователь. Затем меняйте код и ещё раз пройдите финальные сценарии.

Прогресс

0 / 5
x

Вы ещё не решали задания