用 Python 内置 sqlite3 写一个命令行待办清单:建表、增删改查与按截止日期排序

举报
yd_240716394 发表于 2026/10/26 12:08:09 2026/10/26
【摘要】 用 Python 内置 sqlite3 写一个命令行待办清单,含建表、增删改查与按截止日期排序的完整可运行代码,适合学生日常任务管理。

很多待办工具一打开就要注册、同步、开会员,其实对学生和个人开发者来说,一个本地小工具往往更够用。本文用 Python 内置的 sqlite3 模块写一个命令行待办清单,不需要安装第三方库,也不需要启动数据库服务,一个文件就是一个数据库,复制走就能用。

这个小工具要解决什么问题

日常学习里任务很容易散落在聊天记录、备忘录和纸上,最后不是忘了交作业,就是把紧急的事拖到最后。待办清单至少要做到四件事:能随时记下来,能改,能删,能按截止日期排个先后。把数据存在 SQLite 里还有一个好处:关机重启后任务还在,可以按条件查,而不是每次都从头翻一遍。

设计思路:先定表结构,再写命令

一张任务表就够了,不必一开始就设计得很复杂。字段可以这样定:编号是自增主键,标题不能为空,截止日期用文本保存,状态默认是待办,创建时间自动生成。截止日期统一写成 2026-10-28 这样的年月日格式,字符串按字典序排序时就和日期顺序一致,这是后面按截止日期排序的关键。如果格式不统一,有人写 10 月 28 日,有人写 2026/10/28,排序结果就会乱掉。

建表语句里还有一个细节:标题用 NOT NULL 约束,避免存进一条空任务;状态用默认值,新增时不用每次都写。程序启动时先判断表是否存在,不存在就创建,这样第一次运行和之后运行用的是同一份代码,不用手动建库。

完整可运行代码

把下面代码保存为待办脚本文件,直接用 Python 运行即可:

import argparse
import sqlite3
import sys
from datetime import date

DB_PATH = "todo.db"


def get_conn():
    conn = sqlite3.connect(DB_PATH)
    conn.execute("""
        CREATE TABLE IF NOT EXISTS todos (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            title TEXT NOT NULL,
            due_date TEXT,
            status TEXT NOT NULL DEFAULT 'todo',
            created_at TEXT NOT NULL DEFAULT (date('now'))
        )
    """)
    return conn


def add(title, due_date):
    with get_conn() as conn:
        conn.execute(
            "INSERT INTO todos (title, due_date) VALUES (?, ?)",
            (title, due_date),
        )
    print("已添加:" + title)


def list_todos(only_todo=False):
    sql = "SELECT id, title, due_date, status FROM todos"
    if only_todo:
        sql += " WHERE status = 'todo'"
    sql += " ORDER BY due_date IS NULL, due_date ASC, id ASC"
    with get_conn() as conn:
        rows = conn.execute(sql).fetchall()
    if not rows:
        print("暂无任务")
        return
    for task_id, title, due_date, status in rows:
        mark = "已完成" if status == "done" else "待办"
        print(f"{task_id}. [{mark}] {title}  截止:{due_date or '未设置'}")


def done(task_id):
    with get_conn() as conn:
        cur = conn.execute(
            "UPDATE todos SET status = 'done' WHERE id = ?", (task_id,)
        )
    print("已完成" if cur.rowcount else "没有这个编号")


def edit(task_id, title=None, due_date=None):
    if title is None and due_date is None:
        print("请至少给出新的标题或截止日期")
        return
    with get_conn() as conn:
        if title is not None:
            conn.execute(
                "UPDATE todos SET title = ? WHERE id = ?", (title, task_id)
            )
        if due_date is not None:
            conn.execute(
                "UPDATE todos SET due_date = ? WHERE id = ?", (due_date, task_id)
            )
    print("已更新")


def delete(task_id):
    with get_conn() as conn:
        cur = conn.execute("DELETE FROM todos WHERE id = ?", (task_id,))
    print("已删除" if cur.rowcount else "没有这个编号")


def main():
    parser = argparse.ArgumentParser(description="命令行待办清单")
    sub = parser.add_subparsers(dest="cmd", required=True)
    p_add = sub.add_parser("add")
    p_add.add_argument("title")
    p_add.add_argument("--due")
    sub.add_parser("list")
    p_done = sub.add_parser("done")
    p_done.add_argument("id", type=int)
    p_edit = sub.add_parser("edit")
    p_edit.add_argument("id", type=int)
    p_edit.add_argument("--title")
    p_edit.add_argument("--due")
    p_del = sub.add_parser("delete")
    p_del.add_argument("id", type=int)
    args = parser.parse_args()
    if args.cmd == "add":
        add(args.title, args.due)
    elif args.cmd == "list":
        list_todos()
    elif args.cmd == "done":
        done(args.id)
    elif args.cmd == "edit":
        edit(args.id, args.title, args.due)
    elif args.cmd == "delete":
        delete(args.id)


if __name__ == "__main__":
    main()

使用步骤

第一步,保存代码并在代码所在目录打开终端。第二步,添加任务,例如运行添加命令并传入标题与截止日期。第三步,查看清单,运行列表命令,这时任务会按截止日期从早到晚排列,没有截止日期的排在最后。第四步,完成一项后运行完成命令并传入编号,其中的数字是清单前面显示的编号。第五步,需要改期时运行编辑命令并传入新截止日期;不再需要的任务用删除命令并传入编号删除。上面每一步的命令名依次是 add、list、done、edit、delete,脚本名就是保存代码时用的文件名。

为什么排序要这样写

如果只写 ORDER BY due_date ASC,空值在 SQLite 里会排在最前面,结果就是没写日期的任务看起来最紧急,这不符合直觉。加上 due_date IS NULL 这一项后,表达式对有日期的任务是 0,对空值是 1,排序时有日期的先排,空值的后排。同一天截止的任务再按编号排序,保证每次列出的顺序稳定,不会今天一个样、明天又一个样。

几个容易踩的坑

第一,SQL 里一定要用问号占位符传参数,不要把标题直接拼进字符串,否则标题里出现引号就可能报错,也容易写出不安全的代码。第二,写操作后要提交,本文用 with 语句管理连接,正常退出时会自动提交,省去到处写提交的麻烦。第三,删除是不可逆的,误删后不能从这个小工具里找回,重要任务先完成再删,或者先备份数据库文件。第四,截止日期只是文本,程序没有校验它是不是真实日期,输入时自己保持年月日格式即可。

可以继续扩展的方向

这个版本已经能覆盖日常使用,如果还想练手,可以按顺序加功能:先加优先级字段,再按优先级加截止日期双重排序;然后加按关键字搜索;最后加一个导出功能,把待办导出成文本方便打印。每次只加一个功能,跑通再加下一个,比一开始就写一个大而全的程序更容易坚持。本地小工具的价值不在于功能多,而在于随手可用、数据在自己手里、出了问题能自己修。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。