TheZTHEZ / UNDERSTANDING FIRST
Python 项目进阶25 分钟阅读

Python 学习任务管理器

用 JSON 保存学习任务,用命令行添加、查看和标记完成。

项目介绍

把学习任务写入 tasks.json,关闭程序再打开,仍可看到任务与完成进度。这不是网站账号系统,也不跨设备同步,而是一个真正的本地命令行程序。它把“保存状态”变成文件与数据结构,适合作为入门后的第一个小作品。

学习目标

  • 把需求拆成 add、list、done。
  • 用字典表示单个任务,列表表示多个任务。
  • 读写 UTF-8 JSON,处理损坏数据而不盲目覆盖。
  • 用明确编号修改任务,并核对持久化结果。

前置知识与运行环境

需要本地 Python 3 和练习文件夹读写权限,无第三方依赖。安卓与平板可用支持 Python 3 的环境,但需确认应用有目录权限。网页运行器没有本项目所用的命令行操作界面;浏览器虚拟文件不是设备文件,不应照搬流程去管理真实手机数据。

前置:Python 函数、列表与字典、基础文件读写。项目涉及的标准库会在下方解释,不要求首次就记住全部接口。

实现步骤

  1. 创建独立的练习文件夹,保存完整代码为 task-manager.py。
  2. 任务有 id、title、done 三个字段。id 是整数,done 是布尔值。
  3. load_tasks 处理首次没有文件的情况,并验证已保存的数据。
  4. save_tasks 先写临时文件,再 replace 成正式文件,降低普通写入产生半截 JSON 的风险。
  5. argparse 读取子命令。添加与完成后保存,查看时只读。
  6. 每次操作后再次 list,不把“成功提示”当作全部验证。

完整代码

源码 ZIP 的 examples/projects 目录含同一文件。保存完整代码,不要只复制 main。

"""Local CLI task manager. Python 3, standard library only."""
import argparse
import json
from pathlib import Path

DATA_FILE = Path("tasks.json")

def load_tasks():
    if not DATA_FILE.exists():
        return []
    tasks = json.loads(DATA_FILE.read_text(encoding="utf-8"))
    if not isinstance(tasks, list):
        raise ValueError("tasks.json 必须是任务列表")
    seen = set()
    for task in tasks:
        if (not isinstance(task, dict)
                or type(task.get("id")) is not int
                or not isinstance(task.get("title"), str)
                or type(task.get("done")) is not bool
                or task["id"] in seen):
            raise ValueError("tasks.json 的任务格式无效")
        seen.add(task["id"])
    return tasks

def save_tasks(tasks):
    temporary = DATA_FILE.with_suffix(".tmp")
    temporary.write_text(
        json.dumps(tasks, ensure_ascii=False, indent=2), encoding="utf-8")
    temporary.replace(DATA_FILE)

def main():
    parser = argparse.ArgumentParser(description="本地学习任务管理器")
    commands = parser.add_subparsers(dest="command", required=True)
    add = commands.add_parser("add", help="添加任务")
    add.add_argument("title")
    commands.add_parser("list", help="显示任务")
    done = commands.add_parser("done", help="标记完成")
    done.add_argument("id", type=int)
    args = parser.parse_args()
    try:
        tasks = load_tasks()
        if args.command == "add":
            title = args.title.strip()
            if not title or len(title) > 120:
                raise ValueError("标题需要 1–120 个字符")
            task_id = max((task["id"] for task in tasks), default=0) + 1
            tasks.append({"id": task_id, "title": title, "done": False})
            save_tasks(tasks)
            print(f"已添加 #{task_id}:{title}")
        elif args.command == "done":
            task = next((task for task in tasks if task["id"] == args.id), None)
            if task is None:
                raise ValueError("找不到任务编号")
            task["done"] = True
            save_tasks(tasks)
            print(f"已完成 #{args.id}")
        else:
            for task in tasks:
                mark = "✓" if task["done"] else "·"
                print(f"{task['id']} [{mark}] {task['title']}")
            completed = sum(task["done"] for task in tasks)
            print(f"进度:{completed}/{len(tasks)}")
    except (OSError, ValueError) as error:
        parser.exit(1, f"错误:{error}\n")

if __name__ == "__main__":
    main()

代码解释

  • json 负责序列化,ensure_ascii=False 让中文可读,indent=2 改善排版。
  • 新编号取已有最大值加一,不把显示位置当成身份。
  • done 找到对应字典后修改布尔值,重复完成不会重复计数。
  • sum 将真值计为 1,得到完成数量。
  • JSON 格式、值与文件权限错误会以非零退出码结束,便于测试识别。
  • 临时文件替换不等于并发数据库。示例只适合单用户、自己控制的文件夹。

运行方法与验证

在独立练习目录运行:

python task-manager.py add "学习函数"
python task-manager.py add "完成练习"
python task-manager.py list
python task-manager.py done 1
python task-manager.py list

样例数据的查看或预览结果为:

1 [✓] 学习函数
2 [·] 完成练习
进度:1/2

完整代码已在临时目录核对,文件整理的预览、真实移动与冲突分别检查。实际操作时仍需验证文件,不只看成功提示。

常见错误

  • 编号不存在:先 list 核对编号。
  • JSON 损坏:备份旧文件再检查格式,程序不会把损坏内容自动当作空列表覆盖。
  • 权限或路径问题:tasks.json 相对当前工作目录保存,不是相对网页。

拓展方向

增加修改标题、截止日期、筛选未完成与导出 Markdown;为已有命令先写检查,再增加功能。学习文件锁后可改进并发行为,暂不必引入账号或数据库。

这一节,你理解到哪里了?

这是你的自我反馈,与练习估计、课程进度分别记录。

本文为TheZ原创教学内容。正文采用 CC BY-NC-SA 4.0,代码采用 MIT 许可。请尊重署名与授权范围。