项目介绍
把学习任务写入 tasks.json,关闭程序再打开,仍可看到任务与完成进度。这不是网站账号系统,也不跨设备同步,而是一个真正的本地命令行程序。它把“保存状态”变成文件与数据结构,适合作为入门后的第一个小作品。
学习目标
- 把需求拆成 add、list、done。
- 用字典表示单个任务,列表表示多个任务。
- 读写 UTF-8 JSON,处理损坏数据而不盲目覆盖。
- 用明确编号修改任务,并核对持久化结果。
前置知识与运行环境
需要本地 Python 3 和练习文件夹读写权限,无第三方依赖。安卓与平板可用支持 Python 3 的环境,但需确认应用有目录权限。网页运行器没有本项目所用的命令行操作界面;浏览器虚拟文件不是设备文件,不应照搬流程去管理真实手机数据。
前置:Python 函数、列表与字典、基础文件读写。项目涉及的标准库会在下方解释,不要求首次就记住全部接口。
实现步骤
- 创建独立的练习文件夹,保存完整代码为 task-manager.py。
- 任务有 id、title、done 三个字段。id 是整数,done 是布尔值。
- load_tasks 处理首次没有文件的情况,并验证已保存的数据。
- save_tasks 先写临时文件,再 replace 成正式文件,降低普通写入产生半截 JSON 的风险。
- argparse 读取子命令。添加与完成后保存,查看时只读。
- 每次操作后再次 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 许可。请尊重署名与授权范围。