Итоговое консольное приложение
Содержание курса
Пошаговая сборка: функции, файловый ввод-вывод и try-except
Каркас с циклом и хранилищем уже есть. Теперь нужно достроить три вещи: оформить команды как отдельные функции с параметрами, добавить сохранение и загрузку через with open, обернуть уязвимые места в try-except. Делаем это по шагам — в том порядке, в котором они дают рабочее приложение.
Шаг 1. Функция показа заметок
Самая простая из пяти — только перебирает список и печатает:
def show_notes(notes_list):
if not notes_list:
print("Заметок пока нет.")
return
for i, note in enumerate(notes_list, start=1):
print(f"{i}. [{note['id']}] {note['text']}")
Функция принимает список как параметр — не обращается ни к каким глобальным переменным. enumerate даёт порядковый номер для вывода, но удаление всё равно идёт по id, а не по номеру.
Шаг 2. Сохранение и загрузка через with open
Заметки хранятся в памяти как список словарей. В файл удобно записывать каждую заметку одной строкой в формате id|текст, чтобы при загрузке легко разбить обратно через split.
def save_notes(notes_list, filename="notes.txt"):
with open(filename, "w", encoding="utf-8") as f:
for note in notes_list:
f.write(f"{note['id']}|{note['text']}\n")
print("Заметки сохранены.")
def load_notes(notes_list, notes_dict, filename="notes.txt"):
try:
with open(filename, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if "|" not in line:
continue
note_id, text = line.split("|", maxsplit=1)
note = {"id": note_id, "text": text}
notes_list.append(note)
notes_dict[note_id] = note
print(f"Загружено заметок: {len(notes_list)}")
except FileNotFoundError:
print("Файл заметок не найден, начинаем с чистого листа.")
load_notes вызывается один раз — до главного цикла. FileNotFoundError — штатная ситуация при первом запуске, поэтому перехватываем его явно и продолжаем работу. save_notes использует режим "w", который перезаписывает файл целиком. Именно поэтому функция вызывается только при явном «сохранить» или при выходе: если вызывать её после каждой операции, это лишняя запись на диск и повышенный риск испортить файл при сбое посередине записи.
maxsplit=1 в split важен: если в тексте заметки встретится символ |, строка не сломается — разбивка произойдёт только по первому вхождению.
Шаг 3. Когда нужен try-except при вводе пользователя
Не каждый ввод требует try-except. В delete_note, реализованной в предыдущей секции, мы проверяем наличие id через in — это безопасно без try-except: оператор in просто вернёт False, если ключ не найден, и программа выведет сообщение об ошибке.
Но ситуация меняется, если пользователь должен ввести число. Например, если бы вместо строкового id мы принимали числовой индекс заметки, потребовалось бы преобразование int(input(...)). Именно здесь нужен try/except ValueError:
# Гипотетический пример: удаление по числовому индексу
def delete_note_by_index(notes_list, notes_dict):
try:
index = int(input("Номер заметки для удаления: ").strip())
except ValueError:
print("Ошибка: введите целое число.")
return
if 1 <= index <= len(notes_list):
note = notes_list[index - 1]
notes_list.remove(note)
notes_dict.pop(note["id"])
print(f"Заметка удалена.")
else:
print("Нет заметки с таким номером.")
Здесь try-except перехватывает только ValueError — именно то исключение, которое бросает int() при нечисловом вводе. Всё остальное по-прежнему падает явно.
В нашем приложении используется строковый id, поэтому try-except при вводе не нужен. Но принцип важен: оборачивать int(input(...)) в try/except ValueError — стандартный приём везде, где программа ожидает число от пользователя.
Шаг 4. Финальная сборка: точки входа
Всё соединяется в двух местах: инициализации до цикла и обработке «выход» внутри него.
def main():
notes_list = []
notes_dict = {}
load_notes(notes_list, notes_dict) # загрузка при старте
while True:
command = input("Команда (добавить/показать/удалить/сохранить/выход): ").strip().lower()
if command == "добавить":
add_note(notes_list, notes_dict)
elif command == "показать":
show_notes(notes_list)
elif command == "удалить":
delete_note(notes_list, notes_dict)
elif command == "сохранить":
save_notes(notes_list)
elif command == "выход":
save_notes(notes_list) # сохранение перед выходом
break
else:
print("Неизвестная команда.")
if __name__ == "__main__":
main()
if __name__ == "__main__" — стандартная точка входа: если файл запускается напрямую, main() вызывается; если импортируется как модуль, автоматического запуска не происходит.
Оба структуры данных — notes_list и notes_dict — живут в main() и передаются в каждую функцию параметрами. Никаких глобальных переменных нет. load_notes заполняет их до цикла, поэтому в цикл программа входит уже с данными из файла (если он есть). save_notes при выходе гарантирует, что данные записаны в файл после последней сессии.
