Python CLI 待办事项管理器:从零构建一个可持久化的命令行工具

更新时间:2026-10-10 09:59:58 来源:本站整理 浏览:0

目标与知识串联

本文的目标是构建一个真正可运行的命令行待办事项管理器,将 Python 中的列表、字典、函数、文件操作、异常处理以及标准库模块串联起来,最终形成一个能每天使用的实用工具。通过这个项目,你可以巩固 Python 基础语法,并理解如何将零散知识点组合成完整应用。

下表展示了各功能模块与前置知识点的对应关系:

  • 待办项存储:使用字典和列表,涉及列表与字典操作。
  • 功能函数:每个功能独立封装为函数,涉及函数定义与调用。
  • 主循环与输入验证:使用条件判断与循环结构。
  • 文件自动创建:涉及文件操作与异常处理。
  • JSON 数据持久化:使用 json 模块进行序列化与反序列化。
  • 菜单选项映射:通过字典实现选项到函数的映射。
  • 标准库工具:加载 datetime、os 等模块。
  • 类封装(可选进阶):涉及类与对象的概念。

一、整体架构设计

在编写代码前,先明确程序结构。整体是一个菜单驱动的循环程序,用户通过输入数字选择操作,程序执行相应功能并更新数据。

数据存储结构采用 JSON 文件,示例格式如下:

// todos.json
[
  {
    "id": 1,
    "title": "阅读模块与包文章",
    "completed": false,
    "created_at": "2026-04-27 09:15"
  },
  {
    "id": 2,
    "title": "整理 Python 笔记",
    "completed": true,
    "created_at": "2026-04-26 20:30"
  }
]

二、第一步:内存版——添加与查看

首先实现一个不涉及文件操作的简单版本,所有待办项存储在内存列表中,程序退出后数据丢失。此版本用于验证数据结构设计是否合理。

2.1 定义数据结构

每个待办项是一个字典,包含以下字段:

  • id:唯一编号,用于精确定位记录。
  • title:待办内容。
  • completed:布尔值,表示是否已完成。
  • created_at:创建时间字符串。
import datetime

todos = []          # 全局列表,存储所有待办项
next_id = 1        # 下一个可用的 ID

def add_todo(title):
    global next_id
    todo = {
        "id": next_id,
        "title": title,
        "completed": False,
        "created_at": datetime.datetime.now().strftime("%Y-%m-%d %H:%M"),
    }
    todos.append(todo)
    next_id += 1
    print(f"✅ 已添加:{title}")

def list_todos():
    if not todos:
        print("📋 暂无待办事项")
        return
    for todo in todos:
        status = "✓" if todo["completed"] else "○"
        print(f"  [{status}] #{todo['id']} {todo['title']}")

2.2 测试函数

add_todo("阅读模块与包文章")
add_todo("整理 Python 笔记")
add_todo("完成待办管理器 v1")
list_todos()

输出示例:

✅ 已添加:阅读模块与包文章
✅ 已添加:整理 Python 笔记
✅ 已添加:完成待办管理器 v1
  [○] #1 阅读模块与包文章
  [○] #2 整理 Python 笔记
  [○] #3 完成待办管理器 v1

至此,数据结构已验证完毕,后续功能均在此基础上扩展。

三、第二步:标记完成与删除

3.1 标记完成

用户输入待办 ID,程序找到对应项并将其 completed 设为 True:

def complete_todo(todo_id):
    for todo in todos:
        if todo["id"] == todo_id:
            if todo["completed"]:
                print(f"⚠️  #{todo_id} 已经是完成状态")
                return
            todo["completed"] = True
            print(f"🎉 #{todo_id} {todo['title']} 已标记完成")
            return
    print(f"❌ 未找到 ID 为 {todo_id} 的待办项")

3.2 删除待办

def delete_todo(todo_id):
    for i, todo in enumerate(todos):
        if todo["id"] == todo_id:
            removed = todos.pop(i)
            print(f"🗑️  已删除:{removed['title']}")
            return
    print(f"❌ 未找到 ID 为 {todo_id} 的待办项")

注意:pop(i) 按索引删除,但查找时使用 id(内容),删除时使用 enumerate 获取的索引 i。ID 是逻辑编号,不等于列表下标,因此不能直接用 id 作为列表索引。

测试:

complete_todo(2)
delete_todo(3)
list_todos()

输出:

🎉 #2 整理 Python 笔记 已标记完成
🗑️ 已删除:完成待办管理器 v1
  [○] #1 阅读模块与包文章
  [✓] #2 整理 Python 笔记

四、第三步:JSON 文件持久化

内存版本在程序退出后数据会丢失,因此需要添加文件操作,将数据保存到 todos.json 文件中。

4.1 保存与加载函数

import json
import os

FILENAME = "todos.json"

def save_todos():
    """将当前 todos 列表写入 JSON 文件"""
    with open(FILENAME, "w", encoding="utf-8") as f:
        json.dump(todos, f, ensure_ascii=False, indent=2)
    print(f"💾 已保存到 {FILENAME}")

def load_todos():
    """从 JSON 文件加载待办项列表"""
    global next_id
    if not os.path.exists(FILENAME):
        print("📁 首次运行,创建空数据文件")
        return
    with open(FILENAME, "r", encoding="utf-8") as f:
        loaded = json.load(f)
    todos.clear()
    todos.extend(loaded)
    # 恢复 next_id:已有 ID 的最大值 + 1
    if todos:
        next_id = max(t["id"] for t in todos) + 1
    else:
        next_id = 1
    print(f"📂 已从 {FILENAME} 加载 {len(todos)} 条待办")

同时,修改 add_todo、complete_todo、delete_todo 函数,在每次操作后调用 save_todos(),实现“每次修改后立即保存”的策略。这种设计简单可靠,对于数据量小的应用没有性能问题。

五、第四步:主循环与异常处理

5.1 菜单选项到函数的映射

使用字典替代大量的 if/elif 语句,使代码更优雅。菜单定义如下:

MENU = """
========================================
         📝 待办事项管理器
========================================
  1. 添加待办
  2. 查看待办
  3. 标记完成
  4. 删除待办
  5. 退出
========================================
请输入选项(1-5):
"""

主循环实现:

def main():
    load_todos()
    while True:
        try:
            choice = input(MENU).strip()
            if choice == "1":
                title = input("请输入待办内容:").strip()
                if not title:
                    print("⚠️  内容不能为空")
                    continue
                add_todo(title)
            elif choice == "2":
                list_todos()
            elif choice == "3":
                list_todos()
                if not todos:
                    continue
                try:
                    todo_id = int(input("请输入要完成的待办 ID:").strip())
                except ValueError:
                    print("⚠️  请输入数字 ID")
                    continue
                complete_todo(todo_id)
            elif choice == "4":
                list_todos()
                if not todos:
                    continue
                try:
                    todo_id = int(input("请输入要删除的待办 ID:").strip())
                except ValueError:
                    print("⚠️  请输入数字 ID")
                    continue
                delete_todo(todo_id)
            elif choice == "5":
                print("👋 下次见!")
                break
            else:
                print("⚠️  无效选项,请输入 1-5 之间的数字")
        except KeyboardInterrupt:
            print("n👋 强制退出,下次见!")
            break

if __name__ == "__main__":
    main()

5.2 异常处理的三处场景

代码中包含三种不同类型的异常处理,分别解决不同问题:

  • int(input(...)) 可能引发 ValueError,保护用户输入非数字内容。
  • open(FILENAME, "r") 可能引发 FileNotFoundError,已在 load_todos 中处理。
  • input() 外层捕获 KeyboardInterrupt,处理用户按 Ctrl+C 强制退出的情况。

六、完整代码

将所有部分组合,并添加注释和 docstring:

"""待办事项管理器 v1.0
命令行界面,数据持久化到 JSON 文件
"""
import json
import os
import datetime

FILENAME = "todos.json"
todos = []
next_id = 1

def load_todos():
    """从 JSON 文件加载待办项列表,不存在则创建空文件"""
    global next_id
    if not os.path.exists(FILENAME):
        open(FILENAME, "w", encoding="utf-8").close()
        print("📁 首次运行,已创建空数据文件")
        return
    try:
        with open(FILENAME, "r", encoding="utf-8") as f:
            data = json.load(f)
        todos.clear()
        todos.extend(data)
        next_id = (max(t["id"] for t in todos) + 1) if todos else 1
        print(f"📂 已加载 {len(todos)} 条待办")
    except (json.JSONDecodeError, IOError) as e:
        print(f"⚠️  数据文件读取失败,将使用空列表:{e}")

def save_todos():
    """将当前 todos 列表写入 JSON 文件"""
    try:
        with open(FILENAME, "w", encoding="utf-8") as f:
            json.dump(todos, f, ensure_ascii=False, indent=2)
    except IOError as e:
        print(f"❌ 保存失败:{e}")

def add_todo(title):
    """添加一条待办项"""
    global next_id
    todo = {
        "id": next_id,
        "title": title,
        "completed": False,
        "created_at": datetime.datetime.now().strftime("%Y-%m-%d %H:%M"),
    }
    todos.append(todo)
    next_id += 1
    save_todos()
    print(f"✅ 已添加 [{todo['id']}] {title}")

def list_todos(filter_status=None):
    """列出所有待办项,支持按状态过滤"""
    if not todos:
        print("📋 暂无待办事项")
        return
    print("n" + "=" * 40)
    for todo in todos:
        if filter_status is not None and todo["completed"] != filter_status:
            continue
        status = "✓" if todo["completed"] else "○"
        line = f"  [{status}] #{todo['id']} {todo['title']}"
        if todo["completed"]:
            line += f" ({todo['created_at']})"
        print(line)
    print("=" * 40)
    print(f"共 {len(todos)} 条,"
          f"已完成 {sum(1 for t in todos if t['completed'])} 条,"
          f"未完成 {sum(1 for t in todos if not t['completed'])} 条")

def complete_todo(todo_id):
    """将指定 ID 的待办标记为已完成"""
    for todo in todos:
        if todo["id"] == todo_id:
            if todo["completed"]:
                print(f"⚠️  #{todo_id} 已经是完成状态")
                return
            todo["completed"] = True
            save_todos()
            print(f"🎉 #{todo_id} 已标记完成:{todo['title']}")
            return
    print(f"❌ 未找到 ID 为 {todo_id} 的待办项")

def delete_todo(todo_id):
    """删除指定 ID 的待办项"""
    for i, todo in enumerate(todos):
        if todo["id"] == todo_id:
            removed = todos.pop(i)
            save_todos()
            print(f"🗑️  已删除:{removed['title']}")
            return
    print(f"❌ 未找到 ID 为 {todo_id} 的待办项")

def main():
    load_todos()
    MENU = """
========================================
         📝 待办事项管理器 v1.0
========================================
  1. 添加待办
  2. 查看全部
  3. 查看未完成
  4. 查看已完成
  5. 标记完成
  6. 删除待办
  7. 退出
========================================
    """
    while True:
        try:
            choice = input(MENU).strip()
            if choice == "1":
                title = input("请输入待办内容:").strip()
                if not title:
                    print("⚠️  内容不能为空")
                    continue
                add_todo(title)
            elif choice == "2":
                list_todos()
            elif choice == "3":
                list_todos(filter_status=False)
            elif choice == "4":
                list_todos(filter_status=True)
            elif choice == "5":
                list_todos(filter_status=False)
                if not todos:
                    continue
                raw = input("请输入要完成的待办 ID(直接回车取消):").strip()
                if not raw:
                    continue
                try:
                    complete_todo(int(raw))
                except ValueError:
                    print("⚠️  请输入数字")
            elif choice == "6":
                list_todos()
                if not todos:
                    continue
                raw = input("请输入要删除的待办 ID(直接回车取消):").strip()
                if not raw:
                    continue
                try:
                    delete_todo(int(raw))
                except ValueError:
                    print("⚠️  请输入数字")
            elif choice == "7":
                print("👋 下次见!")
                break
            else:
                print("⚠️  无效选项,请输入 1-7")
        except KeyboardInterrupt:
            print("n👋 下次见!")
            break

if __name__ == "__main__":
    main()

七、运行效果

$ python todo_manager.py
📂 已加载 3 条待办
========================================
         📝 待办事项管理器 v1.0
========================================
  1. 添加待办
  2. 查看全部
  3. 查看未完成
  4. 查看已完成
  5. 标记完成
  6. 删除待办
  7. 退出
========================================
请输入选项(1-7):2
========================================
  [○] #1 阅读模块与包文章
  [✓] #2 整理 Python 笔记
  [○] #3 完成待办管理器 v1
========================================
共 3 条,已完成 1 条,未完成 2 条
请输入选项(1-7):1
请输入待办内容:发布文章到 CSDN
💾 已保存到 todos.json
✅ 已添加 [4] 发布文章到 CSDN
请输入选项(1-7):5
========================================
  [○] #1 阅读模块与包文章
  [○] #3 完成待办管理器 v1
  [○] #4 发布文章到 CSDN
========================================
共 3 条,已完成 0 条,未完成 3 条
请输入要完成的待办 ID(直接回车取消):3
💾 已保存到 todos.json
🎉 #3 已标记完成:完成待办管理器 v1
请输入选项(1-7):7
👋 下次见!

程序退出后,todos.json 文件已保存所有数据,下次运行会自动加载。

八、扩展方向

当前版本可正常工作,但真实场景中还需更多功能。以下是几个典型的扩展方向:

  • 优先级(高/中/低):在字典中增加 priority 字段,并使用 sort(key=...) 排序。
  • 截止日期与排序:使用 datetime 模块,按日期排序。
  • 分类标签:使用列表或 set 支持多标签。
  • 命令行参数入口:使用 sys.argv,支持 python todo.py add "内容" 形式。
  • 数据统计汇总:计算并格式化输出统计数据。

九、工程原则

  • 数据结构先行:先确定 JSON 存储结构,再写代码,避免中途修改数据结构导致大量返工。
  • 每个功能一个函数:各函数职责单一,主循环只负责分发任务。
  • 每次修改立即保存:数据量小,即时保存无性能问题,且能防止崩溃时数据丢失。
  • 异常处理分散:不同场景独立处理,无需大 try 包裹整个程序。
  • 用字典做分支映射:添加新选项只需在字典中加一行,保持主循环整洁。

至此,从变量到函数,从数据结构到文件操作,从异常处理到模块化设计,你已经具备了独立编写小型 Python 程序的能力。

原文链接:本站整理

本文来自互联网用户投稿,该文章观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。

如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 service@2cto.cc 进行投诉反馈,一经查实,立即处理!