Python CLI 待办事项管理器:从零构建一个可持久化的命令行工具
目标与知识串联
本文的目标是构建一个真正可运行的命令行待办事项管理器,将 Python 中的列表、字典、函数、文件操作、异常处理以及标准库模块串联起来,最终形成一个能每天使用的实用工具。通过这个项目,你可以巩固 Python 基础语法,并理解如何将零散知识点组合成完整应用。
下表展示了各功能模块与前置知识点的对应关系:
- 待办项存储:使用字典和列表,涉及列表与字典操作。
- 功能函数:每个功能独立封装为函数,涉及函数定义与调用。
- 主循环与输入验证:使用条件判断与循环结构。
- 文件自动创建:涉及文件操作与异常处理。
- JSON 数据持久化:使用 json 模块进行序列化与反序列化。
- 菜单选项映射:通过字典实现选项到函数的映射。
- 标准库工具:加载 datetime、os 等模块。
- 类封装(可选进阶):涉及类与对象的概念。
一、整体架构设计
在编写代码前,先明确程序结构。整体是一个菜单驱动的循环程序,用户通过输入数字选择操作,程序执行相应功能并更新数据。
数据存储结构采用 JSON 文件,示例格式如下:
// todos.json
[
{
"id": 1,
"title": "阅读模块与包文章",
"completed": false,
"created_at": "2026-04-27 09:15"
},
{
"id": 2,
"title": "整理 Python 笔记",
"completed": true,
"created_at": "2026-04-26 20:30"
}
]二、第一步:内存版——添加与查看
首先实现一个不涉及文件操作的简单版本,所有待办项存储在内存列表中,程序退出后数据丢失。此版本用于验证数据结构设计是否合理。
2.1 定义数据结构
每个待办项是一个字典,包含以下字段:
id:唯一编号,用于精确定位记录。title:待办内容。completed:布尔值,表示是否已完成。created_at:创建时间字符串。
import datetime
todos = [] # 全局列表,存储所有待办项
next_id = 1 # 下一个可用的 ID
def add_todo(title):
global next_id
todo = {
"id": next_id,
"title": title,
"completed": False,
"created_at": datetime.datetime.now().strftime("%Y-%m-%d %H:%M"),
}
todos.append(todo)
next_id += 1
print(f"✅ 已添加:{title}")
def list_todos():
if not todos:
print("📋 暂无待办事项")
return
for todo in todos:
status = "✓" if todo["completed"] else "○"
print(f" [{status}] #{todo['id']} {todo['title']}")2.2 测试函数
add_todo("阅读模块与包文章")
add_todo("整理 Python 笔记")
add_todo("完成待办管理器 v1")
list_todos()输出示例:
✅ 已添加:阅读模块与包文章
✅ 已添加:整理 Python 笔记
✅ 已添加:完成待办管理器 v1
[○] #1 阅读模块与包文章
[○] #2 整理 Python 笔记
[○] #3 完成待办管理器 v1至此,数据结构已验证完毕,后续功能均在此基础上扩展。
三、第二步:标记完成与删除
3.1 标记完成
用户输入待办 ID,程序找到对应项并将其 completed 设为 True:
def complete_todo(todo_id):
for todo in todos:
if todo["id"] == todo_id:
if todo["completed"]:
print(f"⚠️ #{todo_id} 已经是完成状态")
return
todo["completed"] = True
print(f"🎉 #{todo_id} {todo['title']} 已标记完成")
return
print(f"❌ 未找到 ID 为 {todo_id} 的待办项")3.2 删除待办
def delete_todo(todo_id):
for i, todo in enumerate(todos):
if todo["id"] == todo_id:
removed = todos.pop(i)
print(f"🗑️ 已删除:{removed['title']}")
return
print(f"❌ 未找到 ID 为 {todo_id} 的待办项")注意:pop(i) 按索引删除,但查找时使用 id(内容),删除时使用 enumerate 获取的索引 i。ID 是逻辑编号,不等于列表下标,因此不能直接用 id 作为列表索引。
测试:
complete_todo(2)
delete_todo(3)
list_todos()输出:
🎉 #2 整理 Python 笔记 已标记完成
🗑️ 已删除:完成待办管理器 v1
[○] #1 阅读模块与包文章
[✓] #2 整理 Python 笔记四、第三步:JSON 文件持久化
内存版本在程序退出后数据会丢失,因此需要添加文件操作,将数据保存到 todos.json 文件中。
4.1 保存与加载函数
import json
import os
FILENAME = "todos.json"
def save_todos():
"""将当前 todos 列表写入 JSON 文件"""
with open(FILENAME, "w", encoding="utf-8") as f:
json.dump(todos, f, ensure_ascii=False, indent=2)
print(f"💾 已保存到 {FILENAME}")
def load_todos():
"""从 JSON 文件加载待办项列表"""
global next_id
if not os.path.exists(FILENAME):
print("📁 首次运行,创建空数据文件")
return
with open(FILENAME, "r", encoding="utf-8") as f:
loaded = json.load(f)
todos.clear()
todos.extend(loaded)
# 恢复 next_id:已有 ID 的最大值 + 1
if todos:
next_id = max(t["id"] for t in todos) + 1
else:
next_id = 1
print(f"📂 已从 {FILENAME} 加载 {len(todos)} 条待办")同时,修改 add_todo、complete_todo、delete_todo 函数,在每次操作后调用 save_todos(),实现“每次修改后立即保存”的策略。这种设计简单可靠,对于数据量小的应用没有性能问题。
五、第四步:主循环与异常处理
5.1 菜单选项到函数的映射
使用字典替代大量的 if/elif 语句,使代码更优雅。菜单定义如下:
MENU = """
========================================
📝 待办事项管理器
========================================
1. 添加待办
2. 查看待办
3. 标记完成
4. 删除待办
5. 退出
========================================
请输入选项(1-5):
"""主循环实现:
def main():
load_todos()
while True:
try:
choice = input(MENU).strip()
if choice == "1":
title = input("请输入待办内容:").strip()
if not title:
print("⚠️ 内容不能为空")
continue
add_todo(title)
elif choice == "2":
list_todos()
elif choice == "3":
list_todos()
if not todos:
continue
try:
todo_id = int(input("请输入要完成的待办 ID:").strip())
except ValueError:
print("⚠️ 请输入数字 ID")
continue
complete_todo(todo_id)
elif choice == "4":
list_todos()
if not todos:
continue
try:
todo_id = int(input("请输入要删除的待办 ID:").strip())
except ValueError:
print("⚠️ 请输入数字 ID")
continue
delete_todo(todo_id)
elif choice == "5":
print("👋 下次见!")
break
else:
print("⚠️ 无效选项,请输入 1-5 之间的数字")
except KeyboardInterrupt:
print("n👋 强制退出,下次见!")
break
if __name__ == "__main__":
main()5.2 异常处理的三处场景
代码中包含三种不同类型的异常处理,分别解决不同问题:
int(input(...))可能引发ValueError,保护用户输入非数字内容。open(FILENAME, "r")可能引发FileNotFoundError,已在load_todos中处理。input()外层捕获KeyboardInterrupt,处理用户按Ctrl+C强制退出的情况。
六、完整代码
将所有部分组合,并添加注释和 docstring:
"""待办事项管理器 v1.0
命令行界面,数据持久化到 JSON 文件
"""
import json
import os
import datetime
FILENAME = "todos.json"
todos = []
next_id = 1
def load_todos():
"""从 JSON 文件加载待办项列表,不存在则创建空文件"""
global next_id
if not os.path.exists(FILENAME):
open(FILENAME, "w", encoding="utf-8").close()
print("📁 首次运行,已创建空数据文件")
return
try:
with open(FILENAME, "r", encoding="utf-8") as f:
data = json.load(f)
todos.clear()
todos.extend(data)
next_id = (max(t["id"] for t in todos) + 1) if todos else 1
print(f"📂 已加载 {len(todos)} 条待办")
except (json.JSONDecodeError, IOError) as e:
print(f"⚠️ 数据文件读取失败,将使用空列表:{e}")
def save_todos():
"""将当前 todos 列表写入 JSON 文件"""
try:
with open(FILENAME, "w", encoding="utf-8") as f:
json.dump(todos, f, ensure_ascii=False, indent=2)
except IOError as e:
print(f"❌ 保存失败:{e}")
def add_todo(title):
"""添加一条待办项"""
global next_id
todo = {
"id": next_id,
"title": title,
"completed": False,
"created_at": datetime.datetime.now().strftime("%Y-%m-%d %H:%M"),
}
todos.append(todo)
next_id += 1
save_todos()
print(f"✅ 已添加 [{todo['id']}] {title}")
def list_todos(filter_status=None):
"""列出所有待办项,支持按状态过滤"""
if not todos:
print("📋 暂无待办事项")
return
print("n" + "=" * 40)
for todo in todos:
if filter_status is not None and todo["completed"] != filter_status:
continue
status = "✓" if todo["completed"] else "○"
line = f" [{status}] #{todo['id']} {todo['title']}"
if todo["completed"]:
line += f" ({todo['created_at']})"
print(line)
print("=" * 40)
print(f"共 {len(todos)} 条,"
f"已完成 {sum(1 for t in todos if t['completed'])} 条,"
f"未完成 {sum(1 for t in todos if not t['completed'])} 条")
def complete_todo(todo_id):
"""将指定 ID 的待办标记为已完成"""
for todo in todos:
if todo["id"] == todo_id:
if todo["completed"]:
print(f"⚠️ #{todo_id} 已经是完成状态")
return
todo["completed"] = True
save_todos()
print(f"🎉 #{todo_id} 已标记完成:{todo['title']}")
return
print(f"❌ 未找到 ID 为 {todo_id} 的待办项")
def delete_todo(todo_id):
"""删除指定 ID 的待办项"""
for i, todo in enumerate(todos):
if todo["id"] == todo_id:
removed = todos.pop(i)
save_todos()
print(f"🗑️ 已删除:{removed['title']}")
return
print(f"❌ 未找到 ID 为 {todo_id} 的待办项")
def main():
load_todos()
MENU = """
========================================
📝 待办事项管理器 v1.0
========================================
1. 添加待办
2. 查看全部
3. 查看未完成
4. 查看已完成
5. 标记完成
6. 删除待办
7. 退出
========================================
"""
while True:
try:
choice = input(MENU).strip()
if choice == "1":
title = input("请输入待办内容:").strip()
if not title:
print("⚠️ 内容不能为空")
continue
add_todo(title)
elif choice == "2":
list_todos()
elif choice == "3":
list_todos(filter_status=False)
elif choice == "4":
list_todos(filter_status=True)
elif choice == "5":
list_todos(filter_status=False)
if not todos:
continue
raw = input("请输入要完成的待办 ID(直接回车取消):").strip()
if not raw:
continue
try:
complete_todo(int(raw))
except ValueError:
print("⚠️ 请输入数字")
elif choice == "6":
list_todos()
if not todos:
continue
raw = input("请输入要删除的待办 ID(直接回车取消):").strip()
if not raw:
continue
try:
delete_todo(int(raw))
except ValueError:
print("⚠️ 请输入数字")
elif choice == "7":
print("👋 下次见!")
break
else:
print("⚠️ 无效选项,请输入 1-7")
except KeyboardInterrupt:
print("n👋 下次见!")
break
if __name__ == "__main__":
main()七、运行效果
$ python todo_manager.py
📂 已加载 3 条待办
========================================
📝 待办事项管理器 v1.0
========================================
1. 添加待办
2. 查看全部
3. 查看未完成
4. 查看已完成
5. 标记完成
6. 删除待办
7. 退出
========================================
请输入选项(1-7):2
========================================
[○] #1 阅读模块与包文章
[✓] #2 整理 Python 笔记
[○] #3 完成待办管理器 v1
========================================
共 3 条,已完成 1 条,未完成 2 条
请输入选项(1-7):1
请输入待办内容:发布文章到 CSDN
💾 已保存到 todos.json
✅ 已添加 [4] 发布文章到 CSDN
请输入选项(1-7):5
========================================
[○] #1 阅读模块与包文章
[○] #3 完成待办管理器 v1
[○] #4 发布文章到 CSDN
========================================
共 3 条,已完成 0 条,未完成 3 条
请输入要完成的待办 ID(直接回车取消):3
💾 已保存到 todos.json
🎉 #3 已标记完成:完成待办管理器 v1
请输入选项(1-7):7
👋 下次见!程序退出后,todos.json 文件已保存所有数据,下次运行会自动加载。
八、扩展方向
当前版本可正常工作,但真实场景中还需更多功能。以下是几个典型的扩展方向:
- 优先级(高/中/低):在字典中增加
priority字段,并使用sort(key=...)排序。 - 截止日期与排序:使用
datetime模块,按日期排序。 - 分类标签:使用列表或
set支持多标签。 - 命令行参数入口:使用
sys.argv,支持python todo.py add "内容"形式。 - 数据统计汇总:计算并格式化输出统计数据。
九、工程原则
- 数据结构先行:先确定 JSON 存储结构,再写代码,避免中途修改数据结构导致大量返工。
- 每个功能一个函数:各函数职责单一,主循环只负责分发任务。
- 每次修改立即保存:数据量小,即时保存无性能问题,且能防止崩溃时数据丢失。
- 异常处理分散:不同场景独立处理,无需大 try 包裹整个程序。
- 用字典做分支映射:添加新选项只需在字典中加一行,保持主循环整洁。
至此,从变量到函数,从数据结构到文件操作,从异常处理到模块化设计,你已经具备了独立编写小型 Python 程序的能力。
原文链接:本站整理
本文来自互联网用户投稿,该文章观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 service@2cto.cc 进行投诉反馈,一经查实,立即处理!
您可能感兴趣的文章:
下一篇:Python执行多行cmd命令:单&与双&&的区别详解