Skip to content

todo cli.py

文件信息

  • 📄 原文件:01_todo_cli.py
  • 🔤 语言:python

项目1:命令行 Todo 应用 一个简单的命令行待办事项管理应用。

功能:

  • 添加待办事项
  • 列出所有待办事项
  • 标记完成
  • 删除待办事项
  • 数据持久化(JSON 文件)

知识点:

  • 类和对象
  • 文件操作
  • JSON 序列化
  • 命令行交互
  • 异常处理

完整代码

python
import json
from dataclasses import dataclass, field, asdict
from datetime import datetime
from pathlib import Path
from typing import List, Optional


@dataclass
class Todo:
    """待办事项"""
    id: int
    title: str
    completed: bool = False
    created_at: str = field(default_factory=lambda: datetime.now().isoformat())
    completed_at: Optional[str] = None

    def complete(self):
        """标记为完成"""
        self.completed = True
        self.completed_at = datetime.now().isoformat()

    def __str__(self):
        status = "✓" if self.completed else "○"
        return f"[{status}] {self.id}. {self.title}"


class TodoApp:
    """Todo 应用"""

    def __init__(self, data_file: str = "todos.json"):
        self.data_file = Path(data_file)
        self.todos: List[Todo] = []
        self.next_id = 1
        self.load()

    def load(self):
        """从文件加载数据"""
        if self.data_file.exists():
            try:
                with open(self.data_file, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                    self.todos = [Todo(**item) for item in data.get('todos', [])]
                    self.next_id = data.get('next_id', 1)
            except (json.JSONDecodeError, KeyError) as e:
                print(f"加载数据失败: {e}")
                self.todos = []
                self.next_id = 1

    def save(self):
        """保存数据到文件"""
        data = {
            'todos': [asdict(todo) for todo in self.todos],
            'next_id': self.next_id
        }
        with open(self.data_file, 'w', encoding='utf-8') as f:
            json.dump(data, f, ensure_ascii=False, indent=2)

    def add(self, title: str) -> Todo:
        """添加待办事项"""
        todo = Todo(id=self.next_id, title=title)
        self.todos.append(todo)
        self.next_id += 1
        self.save()
        return todo

    def list_all(self, show_completed: bool = True) -> List[Todo]:
        """列出所有待办事项"""
        if show_completed:
            return self.todos
        return [t for t in self.todos if not t.completed]

    def get(self, todo_id: int) -> Optional[Todo]:
        """获取指定 ID 的待办事项"""
        for todo in self.todos:
            if todo.id == todo_id:
                return todo
        return None

    def complete(self, todo_id: int) -> bool:
        """标记为完成"""
        todo = self.get(todo_id)
        if todo:
            todo.complete()
            self.save()
            return True
        return False

    def delete(self, todo_id: int) -> bool:
        """删除待办事项"""
        todo = self.get(todo_id)
        if todo:
            self.todos.remove(todo)
            self.save()
            return True
        return False

    def clear_completed(self) -> int:
        """清除所有已完成的待办事项"""
        original_count = len(self.todos)
        self.todos = [t for t in self.todos if not t.completed]
        self.save()
        return original_count - len(self.todos)

    def stats(self) -> dict:
        """获取统计信息"""
        total = len(self.todos)
        completed = sum(1 for t in self.todos if t.completed)
        return {
            'total': total,
            'completed': completed,
            'pending': total - completed
        }


def print_help():
    """打印帮助信息"""
    print("""
命令列表:
  add <title>     添加待办事项
  list            列出所有待办事项
  list pending    只列出未完成的
  done <id>       标记为完成
  del <id>        删除待办事项
  clear           清除所有已完成
  stats           显示统计信息
  help            显示帮助
  quit            退出程序
""")


def main():
    """主函数"""
    print("=" * 50)
    print("       欢迎使用 Python Todo 应用")
    print("=" * 50)
    print("输入 'help' 查看命令列表\n")

    app = TodoApp()

    while True:
        try:
            user_input = input(">>> ").strip()
            if not user_input:
                continue

            parts = user_input.split(maxsplit=1)
            command = parts[0].lower()
            args = parts[1] if len(parts) > 1 else ""

            if command == "quit" or command == "exit":
                print("再见!")
                break

            elif command == "help":
                print_help()

            elif command == "add":
                if not args:
                    print("请提供待办事项标题")
                else:
                    todo = app.add(args)
                    print(f"已添加: {todo}")

            elif command == "list":
                show_all = args.lower() != "pending"
                todos = app.list_all(show_completed=show_all)
                if not todos:
                    print("没有待办事项")
                else:
                    print("\n待办事项列表:")
                    print("-" * 40)
                    for todo in todos:
                        print(f"  {todo}")
                    print("-" * 40)
                    stats = app.stats()
                    print(f"共 {stats['total']} 项,"
                          f"已完成 {stats['completed']} 项,"
                          f"待完成 {stats['pending']}\n")

            elif command == "done":
                try:
                    todo_id = int(args)
                    if app.complete(todo_id):
                        print(f"已完成: {app.get(todo_id)}")
                    else:
                        print(f"未找到 ID 为 {todo_id} 的待办事项")
                except ValueError:
                    print("请提供有效的 ID")

            elif command == "del":
                try:
                    todo_id = int(args)
                    if app.delete(todo_id):
                        print(f"已删除 ID {todo_id}")
                    else:
                        print(f"未找到 ID 为 {todo_id} 的待办事项")
                except ValueError:
                    print("请提供有效的 ID")

            elif command == "clear":
                count = app.clear_completed()
                print(f"已清除 {count} 个已完成的待办事项")

            elif command == "stats":
                stats = app.stats()
                print(f"\n统计信息:")
                print(f"  总数: {stats['total']}")
                print(f"  已完成: {stats['completed']}")
                print(f"  待完成: {stats['pending']}")
                if stats['total'] > 0:
                    rate = stats['completed'] / stats['total'] * 100
                    print(f"  完成率: {rate:.1f}%\n")

            else:
                print(f"未知命令: {command}")
                print("输入 'help' 查看命令列表")

        except KeyboardInterrupt:
            print("\n再见!")
            break
        except Exception as e:
            print(f"错误: {e}")


if __name__ == "__main__":
    main()

💬 讨论

使用 GitHub 账号登录后即可参与讨论

基于 MIT 许可发布