Теория
Как четыре урока сложились в одну программу
Посмотрите, как далеко мы дошли без большого плана на сотню строк. Сначала программа научилась добавлять и показывать дела. Потом мы добавили изменение записей, а затем сохранение в файл. На каждом шаге появлялась одна понятная задача и несколько уже знакомых конструкций.
- add_task создаёт словарь, а save_tasks записывает обновлённый список.
- После перезапуска load_tasks восстанавливает этот словарь из JSON.
- complete_task находит дело по id, меняет done и сохраняет результат.
- 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 означает, что номер вообще не удалось прочитать. А целое число может быть корректным, но не принадлежать ни одному делу. Для него покажем отдельное сообщение «Дело не найдено».
Не путаем новый и повреждённый файл
Отсутствующий файл означает, что программу запустили впервые. Повреждённый JSON — уже проблема с данными, и молчать о ней нельзя. Сообщим, что файл не прочитан, и начнём с пустого списка. При этом не используем голый except: настоящие ошибки в коде по-прежнему должны показать traceback.
Проверяем программу как пользователь
Одна успешная команда ещё не доказывает, что приложение готово. Пройдём несколько обычных и проблемных сценариев подряд. Перед каждым шагом попробуйте предсказать, что останется в списке и в tasks.json, а затем сравните ожидание с результатом.
- Чистый старт. Без файла list показывает пустое состояние.
- Обычная работа. Добавьте два дела, измените одно и отметьте другое.
- Перезапуск. Проверьте текст, отметку и продолжение id.
- Ошибочный ввод. Введите слово и неизвестный номер; меню должно продолжить работу.
- Повреждённый JSON. Программа называет проблему и запускает пустой список.
- Чтение кода. Объясните одним предложением ответственность каждой функции.
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("Неизвестная команда")Большая часть кода осталась прежней. Мы добавили только безопасное чтение номера и общую функцию выбора дела. Именно так удобнее развивать программу: не переписывать рабочие части, а аккуратно исправлять только те места, где обнаружилась проблема.
Наводим порядок без большой переписи
Когда все сценарии проходят, прочитайте файл сверху вниз. По имени каждой функции должно быть понятно, за что она отвечает. Повторное чтение номера лучше оставить в одном месте, а сохранение вызывать только тогда, когда список действительно изменился.
Не переписывайте всю программу ради красоты. Измените одну небольшую часть и снова пройдите тот же сценарий. Если результат не изменился, уборка кода была безопасной.
Практика
Что означает повреждённый JSON
Повреждённый JSON вызывает json.JSONDecodeError; этот случай отличается от отсутствующего файла.
В каком порядке живут данные
Программа загружает прежние дела, меняет и сохраняет список. При следующем запуске она читает уже обновлённые данные.
Почему нужны два сообщения об ошибке
abc вызывает сообщение о формате целого числа, а 99 — сообщение «Дело не найдено».
Почему не нужен голый except
except json.JSONDecodeError обрабатывает только повреждённый JSON. Остальные ошибки по-прежнему будут видны.
Проверяем номера после перезапуска
После двух добавлений следующий id равен 3. Удаление первой записи и перезапуск не уменьшают его.
Итог
Готово: у вас есть небольшое приложение, а не набор разрозненных упражнений. В нём вместе работают ввод, условия, циклы, строки, коллекции, функции, поиск, исключения и файлы.
Сохраните task_manager.py и придумайте для него одну собственную команду — например, показ только незавершённых дел. Сначала запишите, что должен ввести и увидеть пользователь. Затем меняйте код и ещё раз пройдите финальные сценарии.
Прогресс
0 / 5Вы ещё не решали задания