实战篇:命令行待办事项应用

知识库
知识库文档
/tech-stacks/python/tutorial/实战篇:命令行待办事项应用.md

文档

Python 实战篇:构建命令行待办事项应用

前言

学完基础语法后,最好的巩固方式就是动手做一个完整的项目。这篇教程带你从零构建一个命令行待办事项(Todo)应用,涵盖文件持久化、命令解析、错误处理等真实场景。


第 1 章:需求分析

功能列表

  • 添加任务:python todo.py add "买牛奶"
  • 列出所有任务:python todo.py list
  • 完成任务:python todo.py done 1(按编号)
  • 删除任务:python todo.py delete 1
  • 数据持久化到 JSON 文件

项目结构

todo/
├── todo.py          # 入口 + 命令行解析
├── models.py        # 数据模型
├── storage.py       # JSON 文件读写
└── tasks.json       # 持久化文件(运行时生成)

第 2 章:数据模型

创建 models.py

"""数据模型模块"""
from dataclasses import dataclass, field, asdict
from datetime import datetime
from typing import Optional


@dataclass
class Task:
    """任务数据类"""

    title: str
    created_at: str = field(default_factory=lambda: datetime.now().isoformat())
    done: bool = False
    done_at: Optional[str] = None

    def mark_done(self) -> None:
        """标记完成"""
        self.done = True
        self.done_at = datetime.now().isoformat()

    def to_dict(self) -> dict:
        """转为字典(用于 JSON 序列化)"""
        return asdict(self)

    @classmethod
    def from_dict(cls, data: dict) -> "Task":
        """从字典恢复"""
        return cls(**data)

第 3 章:持久化层

创建 storage.py

"""JSON 文件存储模块"""
import json
from pathlib import Path
from typing import List

from models import Task


class TaskStorage:
    """任务的 JSON 文件存储"""

    def __init__(self, filepath: str = "tasks.json"):
        self.filepath = Path(filepath)

    def load(self) -> List[Task]:
        """从文件加载所有任务"""
        if not self.filepath.exists():
            return []
        try:
            data = json.loads(self.filepath.read_text(encoding="utf-8"))
            return [Task.from_dict(item) for item in data]
        except (json.JSONDecodeError, KeyError) as e:
            print(f"⚠️  数据文件损坏: {e},将创建新文件")
            return []

    def save(self, tasks: List[Task]) -> None:
        """保存所有任务到文件"""
        data = [task.to_dict() for task in tasks]
        self.filepath.write_text(
            json.dumps(data, ensure_ascii=False, indent=2),
            encoding="utf-8"
        )

第 4 章:业务逻辑

创建 todo_manager.py

"""任务管理业务逻辑"""
from typing import List

from models import Task
from storage import TaskStorage


class TodoManager:
    """待办事项管理器"""

    def __init__(self, storage: TaskStorage):
        self.storage = storage
        self.tasks: List[Task] = self.storage.load()

    def add(self, title: str) -> Task:
        """添加新任务"""
        task = Task(title=title)
        self.tasks.append(task)
        self.storage.save(self.tasks)
        return task

    def list_all(self, show_done: bool = True) -> List[tuple[int, Task]]:
        """列出任务,返回 (序号, 任务) 列表"""
        result = []
        for i, task in enumerate(self.tasks, start=1):
            if show_done or not task.done:
                result.append((i, task))
        return result

    def mark_done(self, index: int) -> Task:
        """将指定任务标记为完成"""
        task = self._get_task(index)
        if task.done:
            raise ValueError(f"任务 {index} 已经完成了")
        task.mark_done()
        self.storage.save(self.tasks)
        return task

    def delete(self, index: int) -> Task:
        """删除指定任务"""
        task = self._get_task(index)
        removed = self.tasks.pop(index - 1)
        self.storage.save(self.tasks)
        return removed

    def _get_task(self, index: int) -> Task:
        """根据用户看到的序号获取任务"""
        if index < 1 or index > len(self.tasks):
            raise IndexError(
                f"任务编号 {index} 无效,当前共 {len(self.tasks)} 个任务"
            )
        return self.tasks[index - 1]

第 5 章:命令行入口

创建 todo.py

#!/usr/bin/env python3
"""命令行待办事项应用 — 入口"""
import sys
from pathlib import Path

from storage import TaskStorage
from todo_manager import TodoManager


DATA_FILE = Path(__file__).parent / "tasks.json"


def print_task(index: int, task, color: bool = True):
    """格式化打印一个任务"""
    status = "✅" if task.done else "⬜"
    done_info = f" (完成于 {task.done_at[:19]})" if task.done else ""
    print(f"  {status} [{index}] {task.title}{done_info}")


def print_usage():
    """打印使用说明"""
    print("""用法:
  python todo.py add <任务内容>      添加新任务
  python todo.py list                列出所有任务
  python todo.py done <编号>         完成任务
  python todo.py delete <编号>       删除任务
  python todo.py help                显示此帮助""")


def main():
    if len(sys.argv) < 2:
        print_usage()
        return

    command = sys.argv[1].lower()
    storage = TaskStorage(str(DATA_FILE))
    manager = TodoManager(storage)

    try:
        if command == "add":
            if len(sys.argv) < 3:
                print("❌ 请提供任务内容")
                return
            title = " ".join(sys.argv[2:])
            task = manager.add(title)
            print(f"✅ 已添加: {task.title}")

        elif command == "list":
            tasks = manager.list_all()
            if not tasks:
                print("📭 暂无任务,用 add 命令添加吧")
                return
            print(f"\n📋 待办事项 (共 {len(tasks)} 项):")
            for i, task in tasks:
                print_task(i, task)

        elif command == "done":
            if len(sys.argv) < 3:
                print("❌ 请提供任务编号")
                return
            index = int(sys.argv[2])
            task = manager.mark_done(index)
            print(f"🎉 已完成: {task.title}")

        elif command == "delete":
            if len(sys.argv) < 3:
                print("❌ 请提供任务编号")
                return
            index = int(sys.argv[2])
            task = manager.delete(index)
            print(f"🗑️  已删除: {task.title}")

        elif command in ("help", "--help", "-h"):
            print_usage()

        else:
            print(f"❌ 未知命令: {command}")
            print_usage()

    except (IndexError, ValueError) as e:
        print(f"❌ 错误: {e}")
    except Exception as e:
        print(f"❌ 意外错误: {e}")
        raise


if __name__ == "__main__":
    main()

第 6 章:运行与测试

# 进入项目目录
cd todo/

# 添加任务
python todo.py add "学习 Python 装饰器"
python todo.py add "写周报"
python todo.py add "买菜"

# 查看列表
python todo.py list

# 完成第一个任务
python todo.py done 1

# 再次查看(观察变化)
python todo.py list

# 删除已完成的任务
python todo.py delete 1

# 最终查看
python todo.py list

预期输出

✅ 已添加: 学习 Python 装饰器
✅ 已添加: 写周报
✅ 已添加: 买菜

📋 待办事项 (共 3 项):
  ⬜ [1] 学习 Python 装饰器
  ⬜ [2] 写周报
  ⬜ [3] 买菜

🎉 已完成: 学习 Python 装饰器

📋 待办事项 (共 3 项):
  ✅ [1] 学习 Python 装饰器 (完成于 2024-...)
  ⬜ [2] 写周报
  ⬜ [3] 买菜

🗑️  已删除: 学习 Python 装饰器

📋 待办事项 (共 2 项):
  ⬜ [1] 写周报
  ⬜ [2] 买菜

第 7 章:扩展练习

试试自己实现以下功能:

  1. 优先级:任务增加 priority 字段(高/中/低),list 按优先级排序
  2. 搜索python todo.py search "Python" 搜索标题包含关键词的任务
  3. 归档:已完成的超过 7 天的任务自动移到 archive.json
  4. 颜色输出:用 colorama 或 ANSI 转义序列让输出更美观
  5. SQLite 存储:将 TaskStorage 替换为 SQLite 实现

提示:使用 argparse 模块替代手动解析 sys.argv 可以让命令解析更专业。


思考题

  1. 如果多个用户同时操作 tasks.json,会发生什么问题?如何解决?
  2. json.dumpsensure_ascii=False 有什么用?
  3. 为什么 DATA_FILEPath(__file__).parent / "tasks.json" 而不是直接 "tasks.json"
  4. 代码中哪些地方用到了「鸭子类型」?

提示:1. 竞态条件 → 文件锁或数据库;2. 保证中文正常显示;3. 确保文件在脚本所在目录,不受运行目录影响;4. TaskStorageTodoManager 之间只依赖接口而非具体实现。

信息

路径
/tech-stacks/python/tutorial/实战篇:命令行待办事项应用.md
更新时间
2026/5/31