# 第 2-9 课课堂练习:扩展多文件任务管理程序 # # 请先运行 main.py,理解三个模块分别负责什么,再完成下面的扩展。 # 本练习不预置导入、变量、函数或 pass;请根据题目自己编写。 # 练习数据只能保存到当前课程目录的 practice_data 文件夹中。 import pathlib # 第一部分:拆分模块职责 # 1. 新建 practice_store.py,只放“读取和保存任务数据”的函数。 # 2. 新建 practice_service.py,只放“新增、完成、查询任务”的函数。 # 3. 新建 practice_main.py,作为程序入口;从两个模块导入并调用函数。 # 4. 使用 pathlib.Path(__file__).parent 计算当前目录,不手工拼接路径。 LOCAL_PATH = pathlib.Path(__file__).parent # 第二部分:保存与读取任务 # 1. 在 practice_store.py 中导入 json 和 Path。 # 2. 定义 load_tasks(data_file);文件不存在时 return []。 # 3. 使用 read_text(encoding="utf-8") 和 json.loads() 读取数据。 # 4. 捕获 FileNotFoundError 和 json.JSONDecodeError,出现问题时返回 [] 并输出中文提示。 # 5. 定义 save_tasks(data_file, tasks);先创建父目录,再用 json.dumps() 保存。 # 6. 保存中文时使用 ensure_ascii=False 和 encoding="utf-8"。 # 第三部分:新增与完成任务 # 1. 在 practice_service.py 中定义 add_task(tasks, title, priority)。 # 2. title 去掉首尾空白后为空时,使用 raise ValueError("任务标题不能为空。")。 # 3. 新任务包含 id、title、priority、completed 四个键;completed 初始为 False。 # 4. 用列表推导式取得已有 id,并计算下一个 id;空列表的第一个 id 是 1。 # 5. 定义 complete_task(tasks, task_id),找到任务后把 completed 设为 True。 # 6. 找到时 return True,找不到时 return False。 # 第四部分:生成器与汇总 # 1. 定义 generate_pending_tasks(tasks) 生成器函数。 # 2. 只对 completed 为 False 的任务使用 yield。 # 3. 定义 build_task_report(tasks),返回全部、已完成、未完成数量组成的字典。 # 4. 已完成任务可以使用带 if 的列表推导式取得。 # 第五部分:增加调用日志 # 1. 从 functools 导入 wraps。 # 2. 定义 log_call(func) 装饰器,内部定义 wrapper(*args, **kwargs)。 # 3. wrapper 输出“正在执行:函数名”,调用原函数并 return 原结果。 # 4. 为 add_task() 和 complete_task() 添加 @log_call。 # 第六部分:入口程序 # 1. 在 practice_main.py 准备 practice_data/tasks.json 路径。 # 2. 读取已有任务后,新增两条任务:一条高优先级、一条中优先级。 # 3. 把第一条新增任务标记为完成,再保存全部任务。 # 4. 遍历生成器并输出未完成任务。 # 5. 输出汇总结果;再次运行时,能够继续读取上次保存的任务。 # 6. 捕获 ValueError 并输出中文错误提示,不让程序直接崩溃。 # 最终验收测试: # 1. 三个模块职责清晰,入口文件不直接处理 JSON 细节; # 2. 首次运行时能自动创建 practice_data/tasks.json; # 3. 再次运行时能读取已保存的任务; # 4. 空标题不会添加任务,并给出中文提示; # 5. 未完成任务通过包含 yield 的函数逐条获得; # 6. 新增与完成任务时都会输出调用日志; # 7. 汇总数量正确,已完成数和未完成数之和等于总数; # 8. 不把 practice_data、__pycache__ 或虚拟环境提交到 Git。 # 完成后自查: # 1. 是否能说明模块、函数和入口文件各自的职责; # 2. 是否知道 JSON 文件为何要指定 UTF-8; # 3. 是否能解释 try...except 保护的是哪一段可能失败的代码; # 4. 是否能解释列表推导式和生成器各自适合什么场景; # 5. 是否知道装饰器在本项目中为函数增加了什么功能; # 6. 是否写了类型注解,并让返回值与注解一致; # 7. 是否只保存当前课程目录中的练习数据。