文章目录
-
- 一、先看架构
- 二、第一步:内存版------只管添加和查看
-
- [2.1 定义数据结构](#2.1 定义数据结构)
- [2.2 测试这两个函数](#2.2 测试这两个函数)
- 三、第二步:加上标记完成和删除
-
- [3.1 标记完成](#3.1 标记完成)
- [3.2 删除待办](#3.2 删除待办)
- [四、第三步:加上 JSON 文件持久化](#四、第三步:加上 JSON 文件持久化)
-
- [4.1 保存和加载函数](#4.1 保存和加载函数)
- 五、第四步:加上主循环和异常处理
-
- [5.1 菜单选项 → 函数映射](#5.1 菜单选项 → 函数映射)
- [5.2 异常处理的三处场景](#5.2 异常处理的三处场景)
- 六、完整代码
- 七、运行效果
- 八、扩展方向:让工具更接近真实产品
- 九、知识结构图
- 十、工程原则
前九篇文章各有各的知识点------数据类型、函数、异常、类、模块......但它们是分散的。学完容易忘,因为没有机会看到它们在一起工作。
本篇的目标是做一个真正能跑的命令行待办事项管理器。把列表、字典、函数、文件操作、异常处理、标准库全部串联起来,成为一个可以每天使用的工具。
知识点串联表:
| 功能 | 涉及的前置知识点 |
|---|---|
| 待办项存储(dict + 列表) | #04 列表与字典 |
| 每个功能一个函数 | #05 函数入门 |
| 主循环 + 输入验证 | #03 条件判断与循环 |
| 文件不存在时自动创建 | #06 文件操作 + #07 错误与异常 |
| JSON 数据持久化 | #06 json 模块 |
| 菜单选项 → 函数映射 | #03 + #04 字典做映射 |
| 加载标准库工具 | #09 模块与包 |
| 类封装(可选进阶) | #08 类与对象 |
一、先看架构
在写代码之前,先把程序的结构想清楚。整体是一个菜单驱动的循环程序:
1
2
3
4
5
6
7
程序启动
加载 todos.json
(文件不存在则创建空列表)
显示菜单
-
添加 / 2. 查看全部
-
查看未完成 / 4. 查看已完成
-
标记完成 / 6. 删除 / 7. 退出
接收用户输入
输入是什么?
add_todo()
list_todos()
complete_todo()
delete_todo()
退出程序
持久化保存
(写入 todos.json)
程序结束
数据存储结构是这样的:
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:创建时间
python
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 测试这两个函数
python
add_todo("阅读模块与包文章")
add_todo("整理 Python 笔记")
add_todo("完成待办管理器 v1")
list_todos()
输出:
✅ 已添加:阅读模块与包文章
✅ 已添加:整理 Python 笔记
✅ 已添加:完成待办管理器 v1
[○] #1 阅读模块与包文章
[○] #2 整理 Python 笔记
[○] #3 完成待办管理器 v1
关键理解:到这里,待办项的数据结构已经验证完毕。后续所有工作都是在这些基础上加功能,而不是重新设计数据结构。
三、第二步:加上标记完成和删除
3.1 标记完成
用户输入待办 ID,程序找到对应项并将其 completed 设为 True:
python
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 删除待办
python
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 是逻辑编号,不等于列表下标。
测试:
python
complete_todo(2)
delete_todo(3)
list_todos()
输出:
🎉 #2 整理 Python 笔记 已标记完成
🗑️ 已删除:完成待办管理器 v1
[○] #1 阅读模块与包文章
[✓] #2 整理 Python 笔记
四、第三步:加上 JSON 文件持久化
内存版本的致命问题是程序退出后数据全丢了。加一层文件操作,让数据保存到 todos.json。
4.1 保存和加载函数
python
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)} 条待办")
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"✅ 已添加:{title}")
def complete_todo(todo_id):
for todo in todos:
if todo["id"] == todo_id:
todo["completed"] = True
save_todos()
print(f"🎉 #{todo_id} {todo['title']} 已标记完成")
return
print(f"❌ 未找到 ID 为 {todo_id} 的待办项")
def delete_todo(todo_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} 的待办项")
设计说明 :把
save_todos()嵌入了add_todo、complete_todo、delete_todo三个函数------这叫"每次修改后立即保存",简单可靠。复杂系统会用事务或缓冲,但这里每条操作都足够轻量,即时保存没有性能问题。
五、第四步:加上主循环和异常处理
5.1 菜单选项 → 函数映射
用字典替代大量 if/elif,是让代码更优雅的实用技巧:
python
MENU = """
========================================
📝 待办事项管理器
========================================
1. 添加待办
2. 查看待办
3. 标记完成
4. 删除待办
5. 退出
========================================
请输入选项(1-5):
"""
主循环:
python
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:
python
"""
待办事项管理器 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()
七、运行效果
text
$ 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 文件里已经保存了所有数据,下次运行会自动加载:
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"
},
{
"id": 3,
"title": "完成待办管理器 v1",
"completed": true,
"created_at": "2026-04-27 10:00"
},
{
"id": 4,
"title": "发布文章到 CSDN",
"completed": false,
"created_at": "2026-04-27 10:05"
}
]
八、扩展方向:让工具更接近真实产品
当前版本可以正常工作,但真实场景中还需要更多功能。以下是几个典型的扩展方向,每个都能独立成一个小专题:
v1.0 当前版本
v2.0 优先级支持
(高/中/低 三档)
v2.0 截止日期
(datetime + 排序)
v2.0 分类标签
(多对多关系)
v2.0 命令行参数
(sys.argv 入口)
v2.0 数据统计
(完成率/趋势)
| 扩展功能 | 涉及的新知识点 |
|---|---|
| 优先级(高/中/低) | 字典增加 priority 字段,列表排序 sort(key=...) |
| 截止日期 + 排序 | datetime 模块,按日期排序 |
| 分类标签 | 列表的列表或 set,支持多标签 |
| 命令行参数入口 | sys.argv,python todo.py add "内容" |
| 数据统计 | 汇总计算、格式化输出 |
九、知识结构图
毕业项目:待办管理器
数据结构
dict + list
函数划分
每个功能一个函数
主循环
while + try/except
JSON 持久化
json.dump/load
文件异常处理
FileNotFoundError
菜单字典映射
选项 → 函数
标准库工具
datetime/os
十、工程原则
- 数据结构先行:先确定 JSON 存储结构,再写代码------改数据结构代价很高,中途发现结构不对再改会推翻很多代码。
- 每个功能一个函数 :
add_todo、list_todos、complete_todo、delete_todo各司其职,主循环只负责分发任务,不写具体逻辑。 - 每次修改立即保存:这个工具的数据量极小(几百条),每次操作后保存没有性能问题,却能保证程序崩溃时数据不丢失。
- 异常处理分散到各处,而非集中 :
load_todos处理文件不存在,main的循环处理用户输入格式错误和强制退出------每个场景独立处理,不需要一个大 try 包住整个程序。 - 用字典做分支映射:菜单选项 → 函数映射是一个值得养成的习惯------加新选项只需要在字典里加一行,主循环保持整洁。
到这里,从变量到函数,从数据结构到文件操作,从异常处理到模块化设计------这一路走下来,已经具备了独立编写小型 Python 程序的能力。
如果觉得这篇文章有帮助,欢迎点赞、评论和关注。前面的模块与包、类与对象、错误与异常等文章中也有类似的综合实战,可以结合阅读,温故知新。