Day 28:综合练习------命令行 Todo 应用
学了快一个月,到了把知识串联起来的时候。今天做一个完整的命令行 Todo(待办事项)应用,综合运用:文件读写、JSON、函数封装、错误处理、面向对象。
一、需求分析
做一个命令行的待办事项管理工具,支持以下功能:
- 添加任务
- 查看所有任务(带编号和状态)
- 标记任务为完成
- 删除任务
- 数据持久化(用 JSON 保存,下次打开还在)
二、完整代码
python
"""
命令行 Todo 应用
数据存储在 tasks.json 文件中
"""
import json
from datetime import datetime
from pathlib import Path
class Task:
"""单个任务"""
def __init__(self, title, task_id=None, done=False, created_at=None):
self.id = task_id
self.title = title
self.done = done
self.created_at = created_at or datetime.now().isoformat()
def mark_done(self):
self.done = True
def to_dict(self):
"""转为字典(用于 JSON 序列化)"""
return {
"id": self.id,
"title": self.title,
"done": self.done,
"created_at": self.created_at,
}
@classmethod
def from_dict(cls, data):
"""从字典恢复 Task 对象"""
return cls(
title=data["title"],
task_id=data["id"],
done=data["done"],
created_at=data["created_at"],
)
def __str__(self):
status = "✅" if self.done else "⬜"
return f"{status} [{self.id}] {self.title}"
class TodoApp:
"""待办事项管理器"""
def __init__(self, storage_file="tasks.json"):
self.storage_file = Path(storage_file)
self.tasks = []
self.next_id = 1
self.load()
def load(self):
"""从 JSON 文件加载任务"""
if not self.storage_file.exists():
return
try:
data = json.loads(self.storage_file.read_text(encoding="utf-8"))
self.tasks = [Task.from_dict(t) for t in data.get("tasks", [])]
self.next_id = data.get("next_id", 1)
print(f"加载了 {len(self.tasks)} 个任务")
except (json.JSONDecodeError, KeyError):
print("数据文件损坏,将创建新文件")
def save(self):
"""保存到 JSON 文件"""
data = {
"next_id": self.next_id,
"tasks": [t.to_dict() for t in self.tasks],
}
self.storage_file.write_text(
json.dumps(data, ensure_ascii=False, indent=2),
encoding="utf-8",
)
def add(self, title):
"""添加新任务"""
task = Task(title, task_id=self.next_id)
self.tasks.append(task)
self.next_id += 1
self.save()
print(f"添加了任务:{task}")
def list_tasks(self, show_all=True):
"""列出任务"""
tasks = self.tasks if show_all else [t for t in self.tasks if not t.done]
if not tasks:
print("没有任务")
return
print(f"\n{'所有' if show_all else '未完成'}任务(共 {len(tasks)} 个):")
for t in tasks:
print(f" {t}")
def done(self, task_id):
"""标记任务为完成"""
task = self._find(task_id)
if task:
task.mark_done()
self.save()
print(f"已完成:{task}")
else:
print(f"找不到任务 #{task_id}")
def delete(self, task_id):
"""删除任务"""
task = self._find(task_id)
if task:
self.tasks.remove(task)
self.save()
print(f"已删除:{task}")
else:
print(f"找不到任务 #{task_id}")
def _find(self, task_id):
"""根据 ID 查找任务"""
for t in self.tasks:
if t.id == task_id:
return t
return None
def run(self):
"""主循环"""
menu = {
"1": ("添加任务", lambda: self.add(input("任务标题:"))),
"2": ("查看所有", lambda: self.list_tasks()),
"3": ("查看未完成", lambda: self.list_tasks(show_all=False)),
"4": ("标记完成", lambda: self.done(int(input("任务编号:")))),
"5": ("删除任务", lambda: self.delete(int(input("任务编号:")))),
"6": ("退出", None),
}
while True:
print("\n" + "=" * 40)
print("📋 Todo 待办事项")
print("=" * 40)
pending = sum(1 for t in self.tasks if not t.done)
print(f"待完成:{pending} / {len(self.tasks)}")
print("-" * 40)
for key, (desc, _) in menu.items():
print(f" {key}. {desc}")
print("-" * 40)
choice = input("请选择:").strip()
if choice == "6":
print("再见!")
break
elif choice in menu and menu[choice][1]:
try:
menu[choice][1]()
except ValueError as e:
print(f"输入有误:{e}")
else:
print("无效选择,请输入 1-6")
if __name__ == "__main__":
app = TodoApp()
app.run()
三、代码讲解------用了哪些知识?
| 知识 | 在代码中的位置 |
|---|---|
| 类与对象 | Task 类和 TodoApp 类 |
__init__ / __str__ |
Task 的构造和打印 |
| @classmethod | Task.from_dict() 工厂方法 |
| 文件读写 (JSON) | load() 和 save() 方法 |
| pathlib | self.storage_file = Path(...) |
| try/except | load 中捕获 JSON 解析错误 |
| while True + break | run() 主循环 |
| 字典映射取代 if-elif | menu 字典映射菜单选项 |
| lambda | 菜单回调函数 |
| 列表推导式 | [t for t in self.tasks if not t.done] |
四、运行效果
========================================
📋 Todo 待办事项
========================================
待完成:0 / 0
----------------------------------------
1. 添加任务
2. 查看所有
3. 查看未完成
4. 标记完成
5. 删除任务
6. 退出
----------------------------------------
请选择:1
任务标题:学习 Python 面向对象
添加了任务:⬜ [1] 学习 Python 面向对象
五、今日学习总结
| 学习内容 | 掌握情况 | 一句话要点 |
|---|---|---|
| 综合运用 | ✅ 实战 | 把 27 天的知识串成一个完整项目 |
| 项目结构 | ✅ 理解 | 数据类 + 管理类 + 主循环 |
| 数据持久化 | ✅ 重点 | JSON 文件保存状态 |
| 用户交互 | ✅ 理解 | 菜单 + 输入 + 循环 |
今日踩坑记录
- JSON 序列化自定义对象 :
json.dump(Task对象)会报错。需要先用to_dict()转成字典。 - 任务 ID 的生成 :不要用
len(tasks) + 1作为 ID(删除任务后 ID 会重复)。用一个单独的next_id计数器。
六、明天学什么?
继续综合练习------做一个数据分析小工具,读取 CSV 文件,统计、筛选、输出报告。
把知识串起来做的第一个完整项目,哪怕简单,也比写一百个零散练习更有价值。
第28天,打卡完成。明天见!
本系列是个人学习笔记,如有错误欢迎在评论区指正交流。