Учим python. Пишем "Задачник". Часть 2.

Первая часть

Исходники текущего урока

Исходные данные

  • задачник отображает список действий пользователя
  • задачник выводит в консоль выбранное пользователем действием

Задание

  • добавить возможность добавления задач
  • хранить задачи в файле

Решение 1. Добавление задач

Добавим в нашу программу глобальную переменную tasks_list, список задач

list_tasks = []

Описание одной задачи мы будем хранить в словаре, со следующими ключами:

task = {
    # название задачи
    'name': str
    # дата начала задачи
    'date_start': datetime.datetime
    # дата конца задачи
    'date_end': datetime.datetime
    # задача выполнена
    'done': bool
}

Напишем функцию, которая будет добавлять задачу в этот список.

def add_task():
    """
    функция создает и добавляет задачу в список задач,
    по полученным от пользователя данных
    """

    # получаем название задачи от пользователя
    task_name = raw_input(u'Введите название задачи: \n->'.encode('utf-8'))

    # получаем 
    task_date_start = raw_input(u'Введите дату начала задачи (Г.М.Д.): \n->'.encode('utf-8'))
    task_date_end = raw_input(u'Введите дату конца задачи (Г.М.Д.): \n->'.encode('utf-8'))

    # преобразуем даты, полученные от пользователя, из строки в формат datetime
    # если будет ошибка, то выйдем из функции
    try:
        task_date_start = datetime.datetime.strptime(task_date_start, '%Y.%m.%d')
    except ValueError:
        print u'Вы ввели дату начала в неверном формате, надо Г.М.Д.'
        return       
    try:
        task_date_end = datetime.datetime.strptime(task_date_end, '%Y.%m.%d')
    except ValueError:
        print u'Вы ввели дату конца в неверном формате, надо Г.М.Д.'
        return

    # создаем словарь, который характеризует нашу задачу
    new_task = {    # открываем файл для записи
with open(tasks_file_path, 'w') as f:
        'name': task_name,
        'date_start': task_date_start,
        'date_end': task_date_end,
        'done': False
    }

    # добавляем задачу в список задач
    list_tasks.append(new_task)

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

actions = (
    (u'Выход', None),
    (u'Добавить задачу', add_task),
    (u'Изменить задачу', None),
    (u'Удалить задачу', None),
    (u'Удалить выполненные задачи', None),
    (u'Список всех задач', None),
    (u'Список выполненных задач', None),
    (u'Список будущих задач', None),
    (u'Список задач на сегодня', None),
    (u'Список задач на эту неделю', None),
)

У нас есть обработчик для добавления задач, его мы и прописали, а для остальных у нас пока нет обработчиков.

Также мы модифицируем обработку выбранного действия пользователем:

# пользователь выбирает прядковый номер действия
# по пордяковому номеру найдем и название действия
try:
    # в данном блоке может возникнуть ошибка
    # пользователь может ввести число, которого нету в выборе
    action = actions[action_index_int]

    # названеи действия
    action_name = action[0]

    # обработчик действия
    action_callback = action[1]
except IndexError:
    # а тут мы перехватим ошибку, и наша программа продолжит работу
    message = u"Вы не выбрали никакого действия"
else:
    # данный блок выполняется если ошибок не произошло

    # если у нашего действия есть обработчик, то мы его вызовем
    if action_callback is not None:
        action_callback()

    message = u"Вы выбрали: {0}".format(action_name)

И изменим формирование строки опроса действий пользователя

# создадим строку, которая будет выводится при опросе пользователя
actions_help_text = u'Выберите действие:'
for index, action in enumerate(actions):
    # собираем строку
    # порядковый номер действия, действие
    actions_help_text += u'\n{0}. {1}'.format(index, action[0])
actions_help_text += u"\n=>"

Решение 2 задачи. Сохранение списка задач в файл

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

def save_tasks():
    """
    функция, сохраняет список задач в файл
    """

    # открываем файл для записи
    with open('tasks.txt', 'w') as f:
        # записываем в файл список задач, 
        # предварительно список преобразуем в строку 
        f.write(repr(list_tasks))

Теперь добавим вызов этой функции в функцию, которая добавляет задачу в список задач.

def add_task():
    """
    функция создает и добавляет задачу в список задач,
    по полученным от пользователя данных
    """
    ....
    # добавляем задачу в список задач
    list_tasks.append(new_task)

    # сохраняем список задач в файл
    save_tasks()

Загрузка списка задач из файла

Также нам необходимо загружать список задач из файла, при запуске программы

# список задач
list_tasks = []

# загружаем список задач из файла
with open('tasks.txt', 'r') as f:
    list_tasks = eval(f.read())

В идеале, данный код работает, но есть много исключительных ситуации когда этот код сломается:

  • нет файла
  • ошибка при чтении файла

Поэтому необходимо отработать все эти ситуации

Замени код в начале

# список задач
list_tasks = []

на

if os.path.exists('tasks.txt'):
    # если файл существует
    # загружаем список задач из файла
    with open('tasks.txt', 'r') as f:
        try:
            # при загрузке могут возникнуть какие то ошибки
            list_tasks = eval(f.read())
        except Exception:
            # при возникновении любой ошибки, будем считать файл не корректным
            list_tasks = []
        # а если ошибок нет, значит наш список успешно загрузился
else:
    # файла нет, значит и задач ещё нет
    list_tasks = []

И добавим импорт модуля os, для работы с путями

import datetime
import os

Оптимизации

Т.к. мы используем путь к файлу со списком задач в нескольких местах, логично его вынести в переменную, и использовать уже переменную.

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

import datetime
import os

# путь к файлу со списком задач
tasks_file_path = 'tasks.txt'

И далее уже используем именно эту переменную

...
if os.path.exists(tasks_file_path):
    # если файл существует
    # загружаем список задач из файла
    with open(tasks_file_path, 'r') as f:
...
# открываем файл для записи
with open(tasks_file_path, 'w') as f: