🎯 今日目标
完成一个完整的、可运行的待办事项清单程序
实现任务的增、删、改、查、标记完成
数据持久化保存(重启程序数据不丢失)
学会项目功能拆解和模块化设计
体验“给自己写工具”的成就感
📋 一、项目需求分析
我们来设计一个命令行版待办事项清单,包含以下功能:
| 功能 | 说明 |
|---|---|
| 查看所有任务 | 显示所有待办事项,带编号和完成状态 |
| 添加任务 | 在清单末尾添加一个新的待办事项 |
| 删除任务 | 按编号删除指定的任务 |
| 标记完成 | 按编号将任务标记为已完成 |
| 取消完成 | 按编号将已完成的任务改回未完成(反悔功能) |
| 清空所有 | 删除所有任务(有二次确认) |
| 统计信息 | 显示总任务数、已完成数、未完成数 |
| 数据持久化 | 任务保存在todos.txt文件中,重启程序自动加载 |
💡 这个工具虽然简单,但非常实用。很多人就是靠写这样的工具,学会了编程。
🧱 二、数据结构设计
2.1 数据存储格式
我们用一个列表来存储所有任务,每个任务是一个字典:
python
todos = [ {"id": 1, "text": "学习Python", "done": False}, {"id": 2, "text": "写一篇博客", "done": False}, {"id": 3, "text": "完成第14天练习", "done": True}, ]2.2 文件存储格式
保存到文件时,我们用简单的文本格式,每行一个任务:
text
1|学习Python|False 2|写一篇博客|False 3|完成第14天练习|True
用|分隔不同字段,方便读取和解析。
💡 这种简单的文本格式比JSON更直观,新手也更容易理解。
📝 三、代码结构设计
text
todo.py ├── load_todos() # 从文件加载任务 ├── save_todos() # 保存任务到文件 ├── generate_id() # 生成新任务ID ├── show_todos() # 显示所有任务 ├── add_todo() # 添加任务 ├── delete_todo() # 删除任务 ├── complete_todo() # 标记完成 ├── uncomplete_todo() # 取消完成 ├── clear_all() # 清空所有 ├── show_stats() # 显示统计信息 ├── print_menu() # 打印菜单 └── main() # 主程序
📝 四、逐步编写代码
4.1 导入模块和定义常量
python
import os TODO_FILE = "todos.txt"
4.2 加载任务(从文件读取)
python
def load_todos(): """ 从文件加载任务列表 返回: list: 任务列表,每个任务是一个字典 """ todos = [] if not os.path.exists(TODO_FILE): return todos try: with open(TODO_FILE, "r", encoding="utf-8") as f: for line in f: line = line.strip() if not line: continue parts = line.split("|") if len(parts) == 3: todo = { "id": int(parts[0]), "text": parts[1], "done": parts[2] == "True" } todos.append(todo) except Exception as e: print(f"⚠️ 加载数据时出错: {e}") return todos4.3 保存任务(写入文件)
python
def save_todos(todos): """ 保存任务列表到文件 参数: todos: 任务列表 """ try: with open(TODO_FILE, "w", encoding="utf-8") as f: for todo in todos: f.write(f"{todo['id']}|{todo['text']}|{todo['done']}\n") return True except Exception as e: print(f"❌ 保存数据时出错: {e}") return False4.4 生成新ID
python
def generate_id(todos): """ 生成新的任务ID(当前最大ID + 1) 参数: todos: 任务列表 返回: int: 新ID """ if not todos: return 1 return max(todo["id"] for todo in todos) + 1
4.5 显示所有任务
python
def show_todos(todos): """ 显示所有任务 参数: todos: 任务列表 """ if not todos: print("\n📭 暂无待办事项,添加一些吧!") return print("\n" + "=" * 50) print(" 📋 待办清单") print("=" * 50) # 按状态分组显示 undone = [t for t in todos if not t["done"]] done = [t for t in todos if t["done"]] # 显示未完成的 if undone: print("\n⏳ 待完成:") for todo in undone: print(f" [{todo['id']:2d}] {todo['text']}") # 显示已完成的 if done: print("\n✅ 已完成:") for todo in done: print(f" [{todo['id']:2d}] {todo['text']} {chr(10003)}") # ✓ 符号 print("\n" + "-" * 50) print(f"总计: {len(todos)} 项 | 待完成: {len(undone)} 项 | 已完成: {len(done)} 项") print("=" * 50)4.6 添加任务
python
def add_todo(todos): """ 添加新任务 参数: todos: 任务列表 返回: bool: 是否成功 """ text = input("\n请输入新的待办事项: ").strip() if not text: print("❌ 任务内容不能为空") return False todo = { "id": generate_id(todos), "text": text, "done": False } todos.append(todo) save_todos(todos) print(f"✅ 已添加: {text}") return True4.7 删除任务
python
def delete_todo(todos): """ 删除指定编号的任务 参数: todos: 任务列表 """ if not todos: print("\n📭 没有任务可删除") return show_todos(todos) try: todo_id = int(input("\n请输入要删除的任务编号: ")) # 查找任务 for i, todo in enumerate(todos): if todo["id"] == todo_id: confirm = input(f"确认删除 '{todo['text']}'?(y/n): ").lower() if confirm == 'y' or confirm == 'yes': removed = todos.pop(i) save_todos(todos) print(f"✅ 已删除: {removed['text']}") else: print("已取消删除") return print(f"❌ 未找到编号为 {todo_id} 的任务") except ValueError: print("❌ 请输入有效的数字编号")4.8 标记完成
python
def complete_todo(todos): """ 标记任务为已完成 参数: todos: 任务列表 """ undone = [t for t in todos if not t["done"]] if not undone: print("\n🎉 所有任务都已完成!") return show_todos(todos) try: todo_id = int(input("\n请输入要标记完成的任务编号: ")) for todo in todos: if todo["id"] == todo_id: if todo["done"]: print(f"⚠️ '{todo['text']}' 已经完成了") return todo["done"] = True save_todos(todos) print(f"✅ 已标记完成: {todo['text']}") return print(f"❌ 未找到编号为 {todo_id} 的任务") except ValueError: print("❌ 请输入有效的数字编号")4.9 取消完成(反悔功能)
python
def uncomplete_todo(todos): """ 将已完成的任务改回未完成 参数: todos: 任务列表 """ done = [t for t in todos if t["done"]] if not done: print("\n📝 没有已完成的任务") return show_todos(todos) try: todo_id = int(input("\n请输入要取消完成的任务编号: ")) for todo in todos: if todo["id"] == todo_id: if not todo["done"]: print(f"⚠️ '{todo['text']}' 本来就是未完成状态") return todo["done"] = False save_todos(todos) print(f"✅ 已取消完成: {todo['text']}") return print(f"❌ 未找到编号为 {todo_id} 的任务") except ValueError: print("❌ 请输入有效的数字编号")4.10 清空所有任务
python
def clear_all(todos): """ 清空所有任务(二次确认) 参数: todos: 任务列表 """ if not todos: print("\n📭 已经是空清单了") return show_todos(todos) confirm = input("\n⚠️ 确认删除所有任务?(y/n): ").lower() if confirm == 'y' or confirm == 'yes': todos.clear() save_todos(todos) print("🗑️ 已清空所有任务") else: print("已取消操作")4.11 显示统计信息
python
def show_stats(todos): """ 显示统计信息 参数: todos: 任务列表 """ if not todos: print("\n📭 暂无任务") return total = len(todos) done_count = sum(1 for t in todos if t["done"]) undone_count = total - done_count completion_rate = (done_count / total * 100) if total > 0 else 0 print("\n" + "=" * 40) print(" 📊 统计信息") print("=" * 40) print(f"总任务数: {total}") print(f"已完成: {done_count} 项") print(f"待完成: {undone_count} 项") print(f"完成率: {completion_rate:.1f}%") # 显示进度条 bar_length = 20 filled = int(bar_length * completion_rate / 100) bar = "█" * filled + "░" * (bar_length - filled) print(f"进度条: [{bar}]") print("=" * 40)4.12 打印菜单
python
def print_menu(): """打印主菜单""" print("\n" + "=" * 40) print(" 📝 待办事项清单") print("=" * 40) print("1. 查看所有任务") print("2. 添加新任务") print("3. 删除任务") print("4. 标记完成") print("5. 取消完成(反悔)") print("6. 清空所有任务") print("7. 统计信息") print("8. 退出") print("=" * 40)4.13 主程序
python
def main(): """主程序""" # 加载任务 todos = load_todos() print("=" * 40) print(" 📝 待办事项清单") print("=" * 40) print(f"已加载 {len(todos)} 项任务") # 主循环 while True: print_menu() choice = input("请选择操作(1-8): ") if choice == "1": show_todos(todos) elif choice == "2": add_todo(todos) elif choice == "3": delete_todo(todos) elif choice == "4": complete_todo(todos) elif choice == "5": uncomplete_todo(todos) elif choice == "6": clear_all(todos) elif choice == "7": show_stats(todos) elif choice == "8": # 保存数据 save_todos(todos) print("\n👋 再见!数据已保存。") break else: print("❌ 无效选择,请输入 1-8") if __name__ == "__main__": main()📋 五、完整代码
以下是完整的todo.py文件:
python
""" 第14天实战项目:待办事项清单(命令行版) 综合运用:列表、字典、函数、文件操作、异常处理、模块化设计 """ import os TODO_FILE = "todos.txt" def load_todos(): """ 从文件加载任务列表 返回: list: 任务列表,每个任务是一个字典 """ todos = [] if not os.path.exists(TODO_FILE): return todos try: with open(TODO_FILE, "r", encoding="utf-8") as f: for line in f: line = line.strip() if not line: continue parts = line.split("|") if len(parts) == 3: todo = { "id": int(parts[0]), "text": parts[1], "done": parts[2] == "True" } todos.append(todo) except Exception as e: print(f"⚠️ 加载数据时出错: {e}") return todos def save_todos(todos): """ 保存任务列表到文件 参数: todos: 任务列表 """ try: with open(TODO_FILE, "w", encoding="utf-8") as f: for todo in todos: f.write(f"{todo['id']}|{todo['text']}|{todo['done']}\n") return True except Exception as e: print(f"❌ 保存数据时出错: {e}") return False def generate_id(todos): """ 生成新的任务ID(当前最大ID + 1) 参数: todos: 任务列表 返回: int: 新ID """ if not todos: return 1 return max(todo["id"] for todo in todos) + 1 def show_todos(todos): """ 显示所有任务 参数: todos: 任务列表 """ if not todos: print("\n📭 暂无待办事项,添加一些吧!") return print("\n" + "=" * 50) print(" 📋 待办清单") print("=" * 50) undone = [t for t in todos if not t["done"]] done = [t for t in todos if t["done"]] if undone: print("\n⏳ 待完成:") for todo in undone: print(f" [{todo['id']:2d}] {todo['text']}") if done: print("\n✅ 已完成:") for todo in done: print(f" [{todo['id']:2d}] {todo['text']} ✓") print("\n" + "-" * 50) print(f"总计: {len(todos)} 项 | 待完成: {len(undone)} 项 | 已完成: {len(done)} 项") print("=" * 50) def add_todo(todos): """ 添加新任务 参数: todos: 任务列表 返回: bool: 是否成功 """ text = input("\n请输入新的待办事项: ").strip() if not text: print("❌ 任务内容不能为空") return False todo = { "id": generate_id(todos), "text": text, "done": False } todos.append(todo) save_todos(todos) print(f"✅ 已添加: {text}") return True def delete_todo(todos): """ 删除指定编号的任务 参数: todos: 任务列表 """ if not todos: print("\n📭 没有任务可删除") return show_todos(todos) try: todo_id = int(input("\n请输入要删除的任务编号: ")) for i, todo in enumerate(todos): if todo["id"] == todo_id: confirm = input(f"确认删除 '{todo['text']}'?(y/n): ").lower() if confirm == 'y' or confirm == 'yes': removed = todos.pop(i) save_todos(todos) print(f"✅ 已删除: {removed['text']}") else: print("已取消删除") return print(f"❌ 未找到编号为 {todo_id} 的任务") except ValueError: print("❌ 请输入有效的数字编号") def complete_todo(todos): """ 标记任务为已完成 参数: todos: 任务列表 """ undone = [t for t in todos if not t["done"]] if not undone: print("\n🎉 所有任务都已完成!") return show_todos(todos) try: todo_id = int(input("\n请输入要标记完成的任务编号: ")) for todo in todos: if todo["id"] == todo_id: if todo["done"]: print(f"⚠️ '{todo['text']}' 已经完成了") return todo["done"] = True save_todos(todos) print(f"✅ 已标记完成: {todo['text']}") return print(f"❌ 未找到编号为 {todo_id} 的任务") except ValueError: print("❌ 请输入有效的数字编号") def uncomplete_todo(todos): """ 将已完成的任务改回未完成 参数: todos: 任务列表 """ done = [t for t in todos if t["done"]] if not done: print("\n📝 没有已完成的任务") return show_todos(todos) try: todo_id = int(input("\n请输入要取消完成的任务编号: ")) for todo in todos: if todo["id"] == todo_id: if not todo["done"]: print(f"⚠️ '{todo['text']}' 本来就是未完成状态") return todo["done"] = False save_todos(todos) print(f"✅ 已取消完成: {todo['text']}") return print(f"❌ 未找到编号为 {todo_id} 的任务") except ValueError: print("❌ 请输入有效的数字编号") def clear_all(todos): """ 清空所有任务(二次确认) 参数: todos: 任务列表 """ if not todos: print("\n📭 已经是空清单了") return show_todos(todos) confirm = input("\n⚠️ 确认删除所有任务?(y/n): ").lower() if confirm == 'y' or confirm == 'yes': todos.clear() save_todos(todos) print("🗑️ 已清空所有任务") else: print("已取消操作") def show_stats(todos): """ 显示统计信息 参数: todos: 任务列表 """ if not todos: print("\n📭 暂无任务") return total = len(todos) done_count = sum(1 for t in todos if t["done"]) undone_count = total - done_count completion_rate = (done_count / total * 100) if total > 0 else 0 print("\n" + "=" * 40) print(" 📊 统计信息") print("=" * 40) print(f"总任务数: {total}") print(f"已完成: {done_count} 项") print(f"待完成: {undone_count} 项") print(f"完成率: {completion_rate:.1f}%") bar_length = 20 filled = int(bar_length * completion_rate / 100) bar = "█" * filled + "░" * (bar_length - filled) print(f"进度条: [{bar}]") print("=" * 40) def print_menu(): """打印主菜单""" print("\n" + "=" * 40) print(" 📝 待办事项清单") print("=" * 40) print("1. 查看所有任务") print("2. 添加新任务") print("3. 删除任务") print("4. 标记完成") print("5. 取消完成(反悔)") print("6. 清空所有任务") print("7. 统计信息") print("8. 退出") print("=" * 40) def main(): """主程序""" todos = load_todos() print("=" * 40) print(" 📝 待办事项清单") print("=" * 40) print(f"已加载 {len(todos)} 项任务") while True: print_menu() choice = input("请选择操作(1-8): ") if choice == "1": show_todos(todos) elif choice == "2": add_todo(todos) elif choice == "3": delete_todo(todos) elif choice == "4": complete_todo(todos) elif choice == "5": uncomplete_todo(todos) elif choice == "6": clear_all(todos) elif choice == "7": show_stats(todos) elif choice == "8": save_todos(todos) print("\n👋 再见!数据已保存。") break else: print("❌ 无效选择,请输入 1-8") if __name__ == "__main__": main()📸 六、运行结果演示
启动程序:
text
======================================== 📝 待办事项清单 ======================================== 已加载 3 项任务 ======================================== 📝 待办事项清单 ======================================== 1. 查看所有任务 2. 添加新任务 3. 删除任务 4. 标记完成 5. 取消完成(反悔) 6. 清空所有任务 7. 统计信息 8. 退出 ======================================== 请选择操作(1-8): 1 ================================================== 📋 待办清单 ================================================== ⏳ 待完成: [ 1] 学习Python [ 2] 写一篇博客 ✅ 已完成: [ 3] 完成第14天练习 ✓ -------------------------------------------------- 总计: 3 项 | 待完成: 2 项 | 已完成: 1 项 ==================================================
添加任务:
text
请选择操作(1-8): 2 请输入新的待办事项: 去超市买菜 ✅ 已添加: 去超市买菜
标记完成:
text
请选择操作(1-8): 4 ================================================== 📋 待办清单 ================================================== ⏳ 待完成: [ 1] 学习Python [ 2] 写一篇博客 [ 4] 去超市买菜 ✅ 已完成: [ 3] 完成第14天练习 ✓ -------------------------------------------------- 总计: 4 项 | 待完成: 3 项 | 已完成: 1 项 ================================================== 请输入要标记完成的任务编号: 4 ✅ 已标记完成: 去超市买菜
统计信息:
text
请选择操作(1-8): 7 ======================================== 📊 统计信息 ======================================== 总任务数: 4 已完成: 2 项 待完成: 2 项 完成率: 50.0% 进度条: [██████████░░░░░░░░░░] ========================================
🎯 今日总结
今天你通过待办事项清单项目,综合运用了:
| 知识点 | 用在什么地方 |
|---|---|
| 列表 | 存储所有任务 |
| 字典 | 每个任务的“ID、内容、状态” |
| 函数 | 每个功能独立封装 |
| 文件操作 | 读取和保存todos.txt |
| 异常处理 | 输入校验、文件读写错误 |
| 循环 | 主菜单循环 |
| 条件判断 | 菜单路由、状态判断 |
| 列表推导式 | 筛选已完成/未完成的任务 |
| 模块化设计 | 一个函数做一件事 |