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天,打卡完成。明天见!


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

相关推荐
DLYSB_5 小时前
存储运维实战:基于 Ceph Event 监听与 Python 适配器的分布式存储健康度物理声光响应架构
运维·ceph·python·报警灯
阿童木写作6 小时前
跨境图片翻译工具多合一,批量图片视频字幕翻译加智能抠图
人工智能·python·音视频·语音识别
隐积6 小时前
3.1.7.Qt容器大家族(3):QVariant——万能盒子
开发语言·qt
阿童木写作7 小时前
Python实现Temu图片批量翻译自动化教程
运维·人工智能·python·自动化
就是一只白8 小时前
复制地理信息python版本
python
看浪的路人9 小时前
第1讲:Agent 到底是什么?和 Chatbot 有什么区别
python·ai
名字还没想好☜9 小时前
Next.js ‘use client‘ 到底加在哪:Server/Client Components 边界与常见报错
开发语言·前端·javascript·react·next.js
瓦学妹9 小时前
什么是网络索引?它是如何工作的?
大数据·网络·python·网络爬虫
天天爱吃肉821810 小时前
商用车多体动力学实战笔记|第6篇:动力传动系统(发动机、变速箱、分动器、TCS、LSD限滑差速)
大数据·人工智能·笔记·python·嵌入式硬件·汽车
初级代码游戏10 小时前
iOS开发 Swift 速记1:变量和基本数据类型
开发语言·ios·swift