阶段项目1:命令行待办事项管理器
这是一个综合实战项目,将运用前面学到的所有 Python 基础知识,创建一个实用的命令行待办事项管理器。
项目目标
- 综合运用变量、数据结构、控制流程和函数
- 实现文件的读写操作
- 创建用户友好的命令行界面
- 学习项目的组织和结构
项目功能
- [ ] 添加待办事项
- [ ] 查看待办事项列表
- [ ] 标记待办事项为完成
- [ ] 删除待办事项
- [ ] 清空所有待办事项
- [ ] 数据持久化(保存到文件)
- [ ] 搜索待办事项
项目结构
todo-app/
├── todo.py # 主程序
├── todo_manager.py # 待办事项管理器
├── todo.json # 数据存储文件
└── README.md # 项目说明实现步骤
步骤 1:创建数据模型
python
# todo_manager.py
import json
from typing import List, Dict, Optional
from datetime import datetime
from pathlib import Path
class TodoManager:
"""待办事项管理器"""
def __init__(self, data_file: str = "todo.json"):
"""
初始化待办事项管理器
Args:
data_file: 数据存储文件路径
"""
self.data_file = Path(data_file)
self.todos: List[Dict] = []
self.load_todos()
def load_todos(self) -> None:
"""从文件加载待办事项"""
if self.data_file.exists():
try:
with open(self.data_file, 'r', encoding='utf-8') as f:
self.todos = json.load(f)
print(f"✓ 已加载 {len(self.todos)} 条待办事项")
except (json.JSONDecodeError, IOError) as e:
print(f"⚠️ 加载数据失败: {e}")
self.todos = []
else:
print("✓ 创建新的待办事项列表")
self.todos = []
def save_todos(self) -> None:
"""保存待办事项到文件"""
try:
with open(self.data_file, 'w', encoding='utf-8') as f:
json.dump(self.todos, f, ensure_ascii=False, indent=2)
print(f"✓ 已保存 {len(self.todos)} 条待办事项")
except IOError as e:
print(f"❌ 保存数据失败: {e}")步骤 2:实现核心功能
python
# todo_manager.py (继续)
class TodoManager:
# ... 之前的代码 ...
def add_todo(self, title: str, description: str = "") -> bool:
"""
添加待办事项
Args:
title: 待办事项标题
description: 待办事项描述(可选)
Returns:
是否添加成功
"""
if not title.strip():
print("❌ 标题不能为空")
return False
todo = {
"id": self._generate_id(),
"title": title.strip(),
"description": description.strip(),
"completed": False,
"created_at": datetime.now().isoformat(),
"completed_at": None
}
self.todos.append(todo)
self.save_todos()
print(f"✓ 已添加: {title}")
return True
def _generate_id(self) -> int:
"""生成唯一的 ID"""
if not self.todos:
return 1
return max(todo["id"] for todo in self.todos) + 1
def list_todos(self, show_completed: bool = False) -> None:
"""
列出所有待办事项
Args:
show_completed: 是否显示已完成的待办事项
"""
if not self.todos:
print("📝 暂无待办事项")
return
# 过滤待办事项
filtered_todos = self.todos
if not show_completed:
filtered_todos = [t for t in self.todos if not t["completed"]]
if not filtered_todos:
print("📝 暂无待办事项")
return
print(f"\n{'=' * 60}")
print(f"{'待办事项列表'.center(60)}")
print(f"{'=' * 60}")
for todo in filtered_todos:
status = "✓" if todo["completed"] else "○"
print(f"\n[{status}] ID: {todo['id']}")
print(f" 标题: {todo['title']}")
if todo["description"]:
print(f" 描述: {todo['description']}")
print(f" 创建时间: {todo['created_at'][:19]}")
print(f"\n{'=' * 60}")
print(f"总计: {len(filtered_todos)} 条")
print(f"{'=' * 60}")
def complete_todo(self, todo_id: int) -> bool:
"""
标记待办事项为完成
Args:
todo_id: 待办事项 ID
Returns:
是否操作成功
"""
for todo in self.todos:
if todo["id"] == todo_id:
if todo["completed"]:
print(f"⚠️ 待办事项 '{todo['title']}' 已经完成了")
return False
todo["completed"] = True
todo["completed_at"] = datetime.now().isoformat()
self.save_todos()
print(f"✓ 已完成: {todo['title']}")
return True
print(f"❌ 未找到 ID 为 {todo_id} 的待办事项")
return False
def delete_todo(self, todo_id: int) -> bool:
"""
删除待办事项
Args:
todo_id: 待办事项 ID
Returns:
是否删除成功
"""
for i, todo in enumerate(self.todos):
if todo["id"] == todo_id:
title = todo["title"]
self.todos.pop(i)
self.save_todos()
print(f"✓ 已删除: {title}")
return True
print(f"❌ 未找到 ID 为 {todo_id} 的待办事项")
return False
def clear_all(self) -> None:
"""清空所有待办事项"""
if not self.todos:
print("📝 暂无待办事项")
return
confirm = input("确定要清空所有待办事项吗?(yes/no): ")
if confirm.lower() == "yes":
self.todos = []
self.save_todos()
print("✓ 已清空所有待办事项")
else:
print("已取消操作")
def search_todos(self, keyword: str) -> None:
"""
搜索待办事项
Args:
keyword: 搜索关键词
"""
if not keyword.strip():
print("❌ 搜索关键词不能为空")
return
results = [
todo for todo in self.todos
if keyword.lower() in todo["title"].lower() or
keyword.lower() in todo["description"].lower()
]
if not results:
print(f"📝 未找到包含 '{keyword}' 的待办事项")
return
print(f"\n找到 {len(results)} 条结果:")
for todo in results:
status = "✓" if todo["completed"] else "○"
print(f"\n[{status}] ID: {todo['id']}")
print(f" 标题: {todo['title']}")
if todo["description"]:
print(f" 描述: {todo['description']}")
def get_statistics(self) -> Dict[str, int]:
"""
获取统计信息
Returns:
统计信息字典
"""
total = len(self.todos)
completed = sum(1 for t in self.todos if t["completed"])
pending = total - completed
return {
"total": total,
"completed": completed,
"pending": pending
}步骤 3:创建命令行界面
python
# todo.py
from todo_manager import TodoManager
from typing import Optional
def display_menu() -> None:
"""显示主菜单"""
print("\n" + "=" * 40)
print("待办事项管理器".center(40))
print("=" * 40)
print("1. 添加待办事项")
print("2. 查看待办事项")
print("3. 查看所有待办事项(包括已完成)")
print("4. 标记为完成")
print("5. 删除待办事项")
print("6. 搜索待办事项")
print("7. 查看统计信息")
print("8. 清空所有待办事项")
print("0. 退出")
print("=" * 40)
def add_todo_menu(manager: TodoManager) -> None:
"""添加待办事项菜单"""
print("\n--- 添加待办事项 ---")
title = input("标题: ")
description = input("描述(可选): ")
manager.add_todo(title, description)
def complete_todo_menu(manager: TodoManager) -> None:
"""完成待办事项菜单"""
print("\n--- 标记为完成 ---")
manager.list_todos(show_completed=False)
if not manager.todos:
return
try:
todo_id = int(input("\n请输入要完成的待办事项 ID: "))
manager.complete_todo(todo_id)
except ValueError:
print("❌ 请输入有效的 ID")
def delete_todo_menu(manager: TodoManager) -> None:
"""删除待办事项菜单"""
print("\n--- 删除待办事项 ---")
manager.list_todos()
if not manager.todos:
return
try:
todo_id = int(input("\n请输入要删除的待办事项 ID: "))
manager.delete_todo(todo_id)
except ValueError:
print("❌ 请输入有效的 ID")
def search_todo_menu(manager: TodoManager) -> None:
"""搜索待办事项菜单"""
print("\n--- 搜索待办事项 ---")
keyword = input("请输入搜索关键词: ")
manager.search_todos(keyword)
def display_statistics(manager: TodoManager) -> None:
"""显示统计信息"""
print("\n--- 统计信息 ---")
stats = manager.get_statistics()
print(f"总计: {stats['total']} 条")
print(f"已完成: {stats['completed']} 条")
print(f"待完成: {stats['pending']} 条")
if stats['total'] > 0:
completion_rate = (stats['completed'] / stats['total']) * 100
print(f"完成率: {completion_rate:.1f}%")
def main() -> None:
"""主程序"""
manager = TodoManager()
while True:
display_menu()
choice = input("\n请选择操作 (0-8): ")
if choice == "0":
print("\n再见!")
break
elif choice == "1":
add_todo_menu(manager)
elif choice == "2":
manager.list_todos(show_completed=False)
elif choice == "3":
manager.list_todos(show_completed=True)
elif choice == "4":
complete_todo_menu(manager)
elif choice == "5":
delete_todo_menu(manager)
elif choice == "6":
search_todo_menu(manager)
elif choice == "7":
display_statistics(manager)
elif choice == "8":
manager.clear_all()
else:
print("❌ 无效选择,请重试")
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\n\n程序被中断")
except Exception as e:
print(f"\n❌ 发生错误: {e}")完整代码
todo_manager.py
python
import json
from typing import List, Dict, Optional
from datetime import datetime
from pathlib import Path
class TodoManager:
"""待办事项管理器"""
def __init__(self, data_file: str = "todo.json"):
"""
初始化待办事项管理器
Args:
data_file: 数据存储文件路径
"""
self.data_file = Path(data_file)
self.todos: List[Dict] = []
self.load_todos()
def load_todos(self) -> None:
"""从文件加载待办事项"""
if self.data_file.exists():
try:
with open(self.data_file, 'r', encoding='utf-8') as f:
self.todos = json.load(f)
print(f"✓ 已加载 {len(self.todos)} 条待办事项")
except (json.JSONDecodeError, IOError) as e:
print(f"⚠️ 加载数据失败: {e}")
self.todos = []
else:
print("✓ 创建新的待办事项列表")
self.todos = []
def save_todos(self) -> None:
"""保存待办事项到文件"""
try:
with open(self.data_file, 'w', encoding='utf-8') as f:
json.dump(self.todos, f, ensure_ascii=False, indent=2)
print(f"✓ 已保存 {len(self.todos)} 条待办事项")
except IOError as e:
print(f"❌ 保存数据失败: {e}")
def add_todo(self, title: str, description: str = "") -> bool:
"""
添加待办事项
Args:
title: 待办事项标题
description: 待办事项描述(可选)
Returns:
是否添加成功
"""
if not title.strip():
print("❌ 标题不能为空")
return False
todo = {
"id": self._generate_id(),
"title": title.strip(),
"description": description.strip(),
"completed": False,
"created_at": datetime.now().isoformat(),
"completed_at": None
}
self.todos.append(todo)
self.save_todos()
print(f"✓ 已添加: {title}")
return True
def _generate_id(self) -> int:
"""生成唯一的 ID"""
if not self.todos:
return 1
return max(todo["id"] for todo in self.todos) + 1
def list_todos(self, show_completed: bool = False) -> None:
"""
列出所有待办事项
Args:
show_completed: 是否显示已完成的待办事项
"""
if not self.todos:
print("📝 暂无待办事项")
return
# 过滤待办事项
filtered_todos = self.todos
if not show_completed:
filtered_todos = [t for t in self.todos if not t["completed"]]
if not filtered_todos:
print("📝 暂无待办事项")
return
print(f"\n{'=' * 60}")
print(f"{'待办事项列表'.center(60)}")
print(f"{'=' * 60}")
for todo in filtered_todos:
status = "✓" if todo["completed"] else "○"
print(f"\n[{status}] ID: {todo['id']}")
print(f" 标题: {todo['title']}")
if todo["description"]:
print(f" 描述: {todo['description']}")
print(f" 创建时间: {todo['created_at'][:19]}")
print(f"\n{'=' * 60}")
print(f"总计: {len(filtered_todos)} 条")
print(f"{'=' * 60}")
def complete_todo(self, todo_id: int) -> bool:
"""
标记待办事项为完成
Args:
todo_id: 待办事项 ID
Returns:
是否操作成功
"""
for todo in self.todos:
if todo["id"] == todo_id:
if todo["completed"]:
print(f"⚠️ 待办事项 '{todo['title']}' 已经完成了")
return False
todo["completed"] = True
todo["completed_at"] = datetime.now().isoformat()
self.save_todos()
print(f"✓ 已完成: {todo['title']}")
return True
print(f"❌ 未找到 ID 为 {todo_id} 的待办事项")
return False
def delete_todo(self, todo_id: int) -> bool:
"""
删除待办事项
Args:
todo_id: 待办事项 ID
Returns:
是否删除成功
"""
for i, todo in enumerate(self.todos):
if todo["id"] == todo_id:
title = todo["title"]
self.todos.pop(i)
self.save_todos()
print(f"✓ 已删除: {title}")
return True
print(f"❌ 未找到 ID 为 {todo_id} 的待办事项")
return False
def clear_all(self) -> None:
"""清空所有待办事项"""
if not self.todos:
print("📝 暂无待办事项")
return
confirm = input("确定要清空所有待办事项吗?(yes/no): ")
if confirm.lower() == "yes":
self.todos = []
self.save_todos()
print("✓ 已清空所有待办事项")
else:
print("已取消操作")
def search_todos(self, keyword: str) -> None:
"""
搜索待办事项
Args:
keyword: 搜索关键词
"""
if not keyword.strip():
print("❌ 搜索关键词不能为空")
return
results = [
todo for todo in self.todos
if keyword.lower() in todo["title"].lower() or
keyword.lower() in todo["description"].lower()
]
if not results:
print(f"📝 未找到包含 '{keyword}' 的待办事项")
return
print(f"\n找到 {len(results)} 条结果:")
for todo in results:
status = "✓" if todo["completed"] else "○"
print(f"\n[{status}] ID: {todo['id']}")
print(f" 标题: {todo['title']}")
if todo["description"]:
print(f" 描述: {todo['description']}")
def get_statistics(self) -> Dict[str, int]:
"""
获取统计信息
Returns:
统计信息字典
"""
total = len(self.todos)
completed = sum(1 for t in self.todos if t["completed"])
pending = total - completed
return {
"total": total,
"completed": completed,
"pending": pending
}todo.py
python
from todo_manager import TodoManager
from typing import Optional
def display_menu() -> None:
"""显示主菜单"""
print("\n" + "=" * 40)
print("待办事项管理器".center(40))
print("=" * 40)
print("1. 添加待办事项")
print("2. 查看待办事项")
print("3. 查看所有待办事项(包括已完成)")
print("4. 标记为完成")
print("5. 删除待办事项")
print("6. 搜索待办事项")
print("7. 查看统计信息")
print("8. 清空所有待办事项")
print("0. 退出")
print("=" * 40)
def add_todo_menu(manager: TodoManager) -> None:
"""添加待办事项菜单"""
print("\n--- 添加待办事项 ---")
title = input("标题: ")
description = input("描述(可选): ")
manager.add_todo(title, description)
def complete_todo_menu(manager: TodoManager) -> None:
"""完成待办事项菜单"""
print("\n--- 标记为完成 ---")
manager.list_todos(show_completed=False)
if not manager.todos:
return
try:
todo_id = int(input("\n请输入要完成的待办事项 ID: "))
manager.complete_todo(todo_id)
except ValueError:
print("❌ 请输入有效的 ID")
def delete_todo_menu(manager: TodoManager) -> None:
"""删除待办事项菜单"""
print("\n--- 删除待办事项 ---")
manager.list_todos()
if not manager.todos:
return
try:
todo_id = int(input("\n请输入要删除的待办事项 ID: "))
manager.delete_todo(todo_id)
except ValueError:
print("❌ 请输入有效的 ID")
def search_todo_menu(manager: TodoManager) -> None:
"""搜索待办事项菜单"""
print("\n--- 搜索待办事项 ---")
keyword = input("请输入搜索关键词: ")
manager.search_todos(keyword)
def display_statistics(manager: TodoManager) -> None:
"""显示统计信息"""
print("\n--- 统计信息 ---")
stats = manager.get_statistics()
print(f"总计: {stats['total']} 条")
print(f"已完成: {stats['completed']} 条")
print(f"待完成: {stats['pending']} 条")
if stats['total'] > 0:
completion_rate = (stats['completed'] / stats['total']) * 100
print(f"完成率: {completion_rate:.1f}%")
def main() -> None:
"""主程序"""
manager = TodoManager()
while True:
display_menu()
choice = input("\n请选择操作 (0-8): ")
if choice == "0":
print("\n再见!")
break
elif choice == "1":
add_todo_menu(manager)
elif choice == "2":
manager.list_todos(show_completed=False)
elif choice == "3":
manager.list_todos(show_completed=True)
elif choice == "4":
complete_todo_menu(manager)
elif choice == "5":
delete_todo_menu(manager)
elif choice == "6":
search_todo_menu(manager)
elif choice == "7":
display_statistics(manager)
elif choice == "8":
manager.clear_all()
else:
print("❌ 无效选择,请重试")
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\n\n程序被中断")
except Exception as e:
print(f"\n❌ 发生错误: {e}")运行项目
bash
# 运行程序
python3 todo.py
# 示例输出
# ========================================
# 待办事项管理器
# ========================================
# 1. 添加待办事项
# 2. 查看待办事项
# 3. 查看所有待办事项(包括已完成)
# 4. 标记为完成
# 5. 删除待办事项
# 6. 搜索待办事项
# 7. 查看统计信息
# 8. 清空所有待办事项
# 0. 退出
# ========================================扩展功能建议
- 优先级设置:为待办事项添加优先级(高、中、低)
- 标签系统:为待办事项添加标签,便于分类管理
- 截止日期:为待办事项设置截止日期,并显示提醒
- 分类管理:将待办事项按项目或类别分组
- 导入导出:支持导入和导出待办事项(CSV、JSON 格式)
- 颜色输出:使用 ANSI 颜色代码美化输出
- 历史记录:记录已完成的待办事项历史
学习要点
通过这个项目,你学会了:
✅ 数据结构应用
- 使用列表存储待办事项
- 使用字典表示单个待办事项
- 使用列表推导式过滤和转换数据
✅ 函数设计
- 创建可复用的函数
- 使用类型提示提高代码质量
- 使用文档字符串说明函数功能
✅ 文件操作
- 使用 JSON 格式存储数据
- 使用
with语句安全地读写文件 - 处理文件操作中的异常
✅ 用户交互
- 创建命令行界面
- 处理用户输入
- 提供友好的错误提示
✅ 项目组织
- 将代码模块化
- 分离业务逻辑和用户界面
- 使用类封装相关功能
下一章
第6章:面向对象编程 - 深入学习 Python 的面向对象编程,包括类、继承、多态等概念。