使用Python编写一个待办事项管理器(CLI 版)

 更新时间:2026年04月28日 09:34:59   作者:SilentSamsara  
这篇文章主要为大家详细介绍了如何使用Python编写一个命令行待办事项管理器,可以把列表、字典、函数、文件操作、异常处理、标准库全部串联起来,成为一个可以每天使用的工具,感兴趣的小伙伴可以了解下

本篇的目标是做一个真正能跑的命令行待办事项管理器。把列表、字典、函数、文件操作、异常处理、标准库全部串联起来,成为一个可以每天使用的工具。

知识点串联表

功能涉及的前置知识点
待办项存储(dict + 列表)#04 列表与字典
每个功能一个函数#05 函数入门
主循环 + 输入验证#03 条件判断与循环
文件不存在时自动创建#06 文件操作 + #07 错误与异常
JSON 数据持久化#06 json 模块
菜单选项 → 函数映射#03 + #04 字典做映射
加载标准库工具#09 模块与包
类封装(可选进阶)#08 类与对象

一、先看架构

在写代码之前,先把程序的结构想清楚。整体是一个菜单驱动的循环程序

数据存储结构是这样的:

// 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)} 条待办")


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"✅ 已添加:{title}")


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


def delete_todo(todo_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} 的待办项")

设计说明:把 save_todos() 嵌入了 add_todocomplete_tododelete_todo 三个函数——这叫"每次修改后立即保存",简单可靠。复杂系统会用事务或缓冲,但这里每条操作都足够轻量,即时保存没有性能问题。

五、第四步:加上主循环和异常处理

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 文件里已经保存了所有数据,下次运行会自动加载:

[
  {
    "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"
  },
  {
    "id": 3,
    "title": "完成待办管理器 v1",
    "completed": true,
    "created_at": "2026-04-27 10:00"
  },
  {
    "id": 4,
    "title": "发布文章到 CSDN",
    "completed": false,
    "created_at": "2026-04-27 10:05"
  }
]

八、扩展方向:让工具更接近真实产品

当前版本可以正常工作,但真实场景中还需要更多功能。以下是几个典型的扩展方向,每个都能独立成一个小专题:

扩展功能涉及的新知识点
优先级(高/中/低)字典增加 priority 字段,列表排序 sort(key=...)
截止日期 + 排序datetime 模块,按日期排序
分类标签列表的列表或 set,支持多标签
命令行参数入口sys.argvpython todo.py add "内容"
数据统计汇总计算、格式化输出

九、知识结构图

十、工程原则

  1. 数据结构先行:先确定 JSON 存储结构,再写代码——改数据结构代价很高,中途发现结构不对再改会推翻很多代码。
  2. 每个功能一个函数add_todolist_todoscomplete_tododelete_todo 各司其职,主循环只负责分发任务,不写具体逻辑。
  3. 每次修改立即保存:这个工具的数据量极小(几百条),每次操作后保存没有性能问题,却能保证程序崩溃时数据不丢失。
  4. 异常处理分散到各处,而非集中load_todos 处理文件不存在,main 的循环处理用户输入格式错误和强制退出——每个场景独立处理,不需要一个大 try 包住整个程序。
  5. 用字典做分支映射:菜单选项 → 函数映射是一个值得养成的习惯——加新选项只需要在字典里加一行,主循环保持整洁。

到这里,从变量到函数,从数据结构到文件操作,从异常处理到模块化设计——这一路走下来,已经具备了独立编写小型 Python 程序的能力。

到此这篇关于使用Python编写一个待办事项管理器(CLI 版)的文章就介绍到这了,更多相关Python待办事项管理器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Python3.0 实现决策树算法的流程

    Python3.0 实现决策树算法的流程

    这篇文章主要介绍了Python3.0 实现决策树算法的流程,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-08-08
  • python单元测试unittest实例详解

    python单元测试unittest实例详解

    这篇文章主要介绍了python单元测试unittest用法,以实例形式详细分析了Python中单元测试的概念、用法与相关使用技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-05-05
  • Python 中将值附加到集合的操作方法

    Python 中将值附加到集合的操作方法

    这篇文章主要介绍了Python 中将值附加到集合的操作方法,通过使用 add() 方法或 update() 方法,你可以向 Python 中的集合中添加元素,在添加元素时,需要注意不允许重复元素和集合是无序的,本文通过示例代码给大家介绍的非常详细,需要的朋友可以参考下
    2023-05-05
  • 使用Python开发一个Ditto剪贴板数据导出工具

    使用Python开发一个Ditto剪贴板数据导出工具

    在日常工作中,我们经常需要处理大量的剪贴板数据,下面将介绍如何使用Python的wxPython库开发一个图形化工具,实现从Ditto数据库中读取、选择和导出剪贴板历史记录的功能
    2025-08-08
  • Python中parsel两种获取数据方式小结

    Python中parsel两种获取数据方式小结

    本文主要介绍了Python中parsel两种获取数据方式小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-04-04
  • Python shapefile转GeoJson的2种方式实例

    Python shapefile转GeoJson的2种方式实例

    geojson是地图可视化系统中最常用的地理数据格式,几乎所有主流地图可视化库或框架都支持geojson数据的加载,下面这篇文章主要给大家介绍了关于Python shapefile转GeoJson的2种方式的相关资料,需要的朋友可以参考下
    2023-03-03
  • python--shutil移动文件到另一个路径的操作

    python--shutil移动文件到另一个路径的操作

    这篇文章主要介绍了python--shutil移动文件到另一个路径的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-07-07
  • Ubuntu18.04下python版本完美切换的解决方法

    Ubuntu18.04下python版本完美切换的解决方法

    这篇文章主要为大家详细介绍了Ubuntu18.04下python版本完美切换的解决方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-06-06
  • Python中unittest模块做UT(单元测试)使用实例

    Python中unittest模块做UT(单元测试)使用实例

    这篇文章主要介绍了Python中unittest模块做UT(单元测试)使用实例,本文直接给出待测试的类、测试类和测试结果以及测试总结,需要的朋友可以参考下
    2015-06-06
  • 基于Faker生成测试数据的示例代码

    基于Faker生成测试数据的示例代码

    Faker是用来生成测试数据的Python包,可以很方便地生成各种测试数据,支持中文,支持命令行模式,下面小编就来和大家详细讲讲它的具体使用吧
    2025-06-06

最新评论