day-028-综合练习-命令行工具

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 文件保存状态
用户交互 ✅ 理解 菜单 + 输入 + 循环

今日踩坑记录

  1. JSON 序列化自定义对象json.dump(Task对象) 会报错。需要先用 to_dict() 转成字典。
  2. 任务 ID 的生成 :不要用 len(tasks) + 1 作为 ID(删除任务后 ID 会重复)。用一个单独的 next_id 计数器。

六、明天学什么?

继续综合练习------做一个数据分析小工具,读取 CSV 文件,统计、筛选、输出报告。


把知识串起来做的第一个完整项目,哪怕简单,也比写一百个零散练习更有价值。
第28天,打卡完成。明天见!


本系列是个人学习笔记,如有错误欢迎在评论区指正交流。

相关推荐
泡泡鱼(敲代码中)1 小时前
Python 字符串 str 完整学习笔记
python·学习
troy1281 小时前
Python 基础语法(八):Web 后端开发、数据分析与可视化、网络爬虫、人工智能 / 大模型应用
前端·python·数据分析
菜鸟~noob2331 小时前
【电子战】第15篇:混合定位——TDOA/FDOA/AOA 联合【含matlab代码】
开发语言·matlab
我不会起名字3221 小时前
一天一道算法题(34):回溯法的经典例题(子集)
java·数据结构·python·算法·golang·深度优先·力扣
ThornArmor2 小时前
重铸1996|肉体渲染:关节的裂缝:分段模型积木拼装与未成熟的 RSP 矩阵骨骼动画形变
c语言·汇编·c++·python·硬件架构·游戏机
毕业设计7032 小时前
(免费领源码)基于Python的博物馆研学活动管理系统的设计与实现22724- java、PHP、python、C#、小程序、大数据、单片机、网络工程等)
python·mysql·随机森林·pycharm·django·flask·推荐算法
kyrie_sakura2 小时前
python学习笔记15 -- Anaconda,Jupyter,numpy
python·学习·jupyter·numpy
Dreams_l2 小时前
死信队列和延迟队列介绍
java·开发语言
测试老哥3 小时前
接口测试的测试用例应该怎么写?
自动化测试·软件测试·python·测试工具·职场和发展·测试用例·接口测试
Zenova EdgeOS3 小时前
Python 工业边缘 redis-py 异步实战
开发语言·redis·python