使用Python編寫一個待辦事項管理器(CLI?版)
本篇的目標(biāo)是做一個真正能跑的命令行待辦事項管理器。把列表、字典、函數(shù)、文件操作、異常處理、標(biāo)準(zhǔn)庫全部串聯(lián)起來,成為一個可以每天使用的工具。
知識點(diǎn)串聯(lián)表:
| 功能 | 涉及的前置知識點(diǎn) |
|---|---|
| 待辦項存儲(dict + 列表) | #04 列表與字典 |
| 每個功能一個函數(shù) | #05 函數(shù)入門 |
| 主循環(huán) + 輸入驗(yàn)證 | #03 條件判斷與循環(huán) |
| 文件不存在時自動創(chuàng)建 | #06 文件操作 + #07 錯誤與異常 |
| JSON 數(shù)據(jù)持久化 | #06 json 模塊 |
| 菜單選項 → 函數(shù)映射 | #03 + #04 字典做映射 |
| 加載標(biāo)準(zhǔn)庫工具 | #09 模塊與包 |
| 類封裝(可選進(jìn)階) | #08 類與對象 |
一、先看架構(gòu)
在寫代碼之前,先把程序的結(jié)構(gòu)想清楚。整體是一個菜單驅(qū)動的循環(huán)程序:

數(shù)據(jù)存儲結(jié)構(gòu)是這樣的:
// 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"
}
]二、第一步:內(nèi)存版——只管添加和查看
先寫一個最簡單版本,不涉及文件操作。所有待辦項存在內(nèi)存列表里,程序退出就消失——這個版本用來驗(yàn)證數(shù)據(jù)結(jié)構(gòu)是否合理。
2.1 定義數(shù)據(jù)結(jié)構(gòu)
每個待辦項是一個字典,包含:
id:唯一編號,用于精確定位每條記錄title:待辦內(nèi)容completed:是否已完成(布爾值)created_at:創(chuàng)建時間
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 測試這兩個函數(shù)
add_todo("閱讀模塊與包文章")
add_todo("整理 Python 筆記")
add_todo("完成待辦管理器 v1")
list_todos()
輸出:
? 已添加:閱讀模塊與包文章
? 已添加:整理 Python 筆記
? 已添加:完成待辦管理器 v1
[○] #1 閱讀模塊與包文章
[○] #2 整理 Python 筆記
[○] #3 完成待辦管理器 v1
關(guān)鍵理解:到這里,待辦項的數(shù)據(jù)結(jié)構(gòu)已經(jīng)驗(yàn)證完畢。后續(xù)所有工作都是在這些基礎(chǔ)上加功能,而不是重新設(shè)計數(shù)據(jù)結(jié)構(gòu)。
三、第二步:加上標(biāo)記完成和刪除
3.1 標(biāo)記完成
用戶輸入待辦 ID,程序找到對應(yīng)項并將其 completed 設(shè)為 True:
def complete_todo(todo_id):
for todo in todos:
if todo["id"] == todo_id:
if todo["completed"]:
print(f"?? #{todo_id} 已經(jīng)是完成狀態(tài)")
return
todo["completed"] = True
print(f"?? #{todo_id} {todo['title']} 已標(biāo)記完成")
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(內(nèi)容),刪除時用的是 enumerate 獲取的索引 i。這兩個操作必須配合,不能直接用 id 作為列表索引,因?yàn)?ID 是邏輯編號,不等于列表下標(biāo)。
測試:
complete_todo(2) delete_todo(3) list_todos()
輸出:
?? #2 整理 Python 筆記 已標(biāo)記完成
??? 已刪除:完成待辦管理器 v1
[○] #1 閱讀模塊與包文章
[?] #2 整理 Python 筆記
四、第三步:加上 JSON 文件持久化
內(nèi)存版本的致命問題是程序退出后數(shù)據(jù)全丟了。加一層文件操作,讓數(shù)據(jù)保存到 todos.json。
4.1 保存和加載函數(shù)
import json
import os
FILENAME = "todos.json"
def save_todos():
"""將當(dāng)前 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("?? 首次運(yùn)行,創(chuàng)建空數(shù)據(jù)文件")
return
with open(FILENAME, "r", encoding="utf-8") as f:
loaded = json.load(f)
todos.clear()
todos.extend(loaded)
# 恢復(fù) 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']} 已標(biāo)記完成")
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} 的待辦項")
設(shè)計說明:把 save_todos() 嵌入了 add_todo、complete_todo、delete_todo 三個函數(shù)——這叫"每次修改后立即保存",簡單可靠。復(fù)雜系統(tǒng)會用事務(wù)或緩沖,但這里每條操作都足夠輕量,即時保存沒有性能問題。
五、第四步:加上主循環(huán)和異常處理
5.1 菜單選項 → 函數(shù)映射
用字典替代大量 if/elif,是讓代碼更優(yōu)雅的實(shí)用技巧:
MENU = """
========================================
?? 待辦事項管理器
========================================
1. 添加待辦
2. 查看待辦
3. 標(biāo)記完成
4. 刪除待辦
5. 退出
========================================
請輸入選項(1-5):
"""
主循環(huán):
def main():
load_todos()
while True:
try:
choice = input(MENU).strip()
if choice == "1":
title = input("請輸入待辦內(nèi)容:").strip()
if not title:
print("?? 內(nèi)容不能為空")
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("?? 請輸入數(shù)字 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("?? 請輸入數(shù)字 ID")
continue
delete_todo(todo_id)
elif choice == "5":
print("?? 下次見!")
break
else:
print("?? 無效選項,請輸入 1-5 之間的數(shù)字")
except KeyboardInterrupt:
print("\n?? 強(qiáng)制退出,下次見!")
break
if __name__ == "__main__":
main()
5.2 異常處理的三處場景
這段代碼里有三處不同類型的異常處理,各自解決不同問題:
| 位置 | 異常類型 | 保護(hù)什么 |
|---|---|---|
int(input(...)) | ValueError | 用戶輸入了非數(shù)字內(nèi)容 |
open(FILENAME, "r") | FileNotFoundError | 數(shù)據(jù)文件不存在(load_todos 里已處理) |
input() 外層 | KeyboardInterrupt | 用戶按 Ctrl+C 強(qiáng)制退出 |
六、完整代碼
把所有部分組合在一起,加上注釋和 docstring:
"""
待辦事項管理器 v1.0
命令行界面,數(shù)據(jù)持久化到 JSON 文件
"""
import json
import os
import datetime
FILENAME = "todos.json"
todos = []
next_id = 1
def load_todos():
"""從 JSON 文件加載待辦項列表,不存在則創(chuàng)建空文件"""
global next_id
if not os.path.exists(FILENAME):
open(FILENAME, "w", encoding="utf-8").close()
print("?? 首次運(yùn)行,已創(chuàng)建空數(shù)據(jù)文件")
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"?? 數(shù)據(jù)文件讀取失敗,將使用空列表:{e}")
def save_todos():
"""將當(dāng)前 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):
"""列出所有待辦項,支持按狀態(tài)過濾"""
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 的待辦標(biāo)記為已完成"""
for todo in todos:
if todo["id"] == todo_id:
if todo["completed"]:
print(f"?? #{todo_id} 已經(jīng)是完成狀態(tài)")
return
todo["completed"] = True
save_todos()
print(f"?? #{todo_id} 已標(biāo)記完成:{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. 標(biāo)記完成
6. 刪除待辦
7. 退出
========================================
"""
while True:
try:
choice = input(MENU).strip()
if choice == "1":
title = input("請輸入待辦內(nèi)容:").strip()
if not title:
print("?? 內(nèi)容不能為空")
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("?? 請輸入數(shù)字")
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("?? 請輸入數(shù)字")
elif choice == "7":
print("?? 下次見!")
break
else:
print("?? 無效選項,請輸入 1-7")
except KeyboardInterrupt:
print("\n?? 下次見!")
break
if __name__ == "__main__":
main()
七、運(yùn)行效果
$ python todo_manager.py
?? 已加載 3 條待辦
========================================
?? 待辦事項管理器 v1.0
========================================
1. 添加待辦
2. 查看全部
3. 查看未完成
4. 查看已完成
5. 標(biāo)記完成
6. 刪除待辦
7. 退出
========================================
請輸入選項(1-7):
2
========================================
[○] #1 閱讀模塊與包文章
[?] #2 整理 Python 筆記
[○] #3 完成待辦管理器 v1
========================================
共 3 條,已完成 1 條,未完成 2 條
請輸入選項(1-7):
1
請輸入待辦內(nèi)容:發(fā)布文章到 CSDN
?? 已保存到 todos.json
? 已添加 [4] 發(fā)布文章到 CSDN
請輸入選項(1-7):
5
========================================
[○] #1 閱讀模塊與包文章
[○] #3 完成待辦管理器 v1
[○] #4 發(fā)布文章到 CSDN
========================================
共 3 條,已完成 0 條,未完成 3 條
請輸入要完成的待辦 ID(直接回車取消):3
?? 已保存到 todos.json
?? #3 已標(biāo)記完成:完成待辦管理器 v1
請輸入選項(1-7):
7
?? 下次見!
程序退出后,todos.json 文件里已經(jīng)保存了所有數(shù)據(jù),下次運(yùn)行會自動加載:
[
{
"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": "發(fā)布文章到 CSDN",
"completed": false,
"created_at": "2026-04-27 10:05"
}
]八、擴(kuò)展方向:讓工具更接近真實(shí)產(chǎn)品
當(dāng)前版本可以正常工作,但真實(shí)場景中還需要更多功能。以下是幾個典型的擴(kuò)展方向,每個都能獨(dú)立成一個小專題:

| 擴(kuò)展功能 | 涉及的新知識點(diǎn) |
|---|---|
| 優(yōu)先級(高/中/低) | 字典增加 priority 字段,列表排序 sort(key=...) |
| 截止日期 + 排序 | datetime 模塊,按日期排序 |
| 分類標(biāo)簽 | 列表的列表或 set,支持多標(biāo)簽 |
| 命令行參數(shù)入口 | sys.argv,python todo.py add "內(nèi)容" |
| 數(shù)據(jù)統(tǒng)計 | 匯總計算、格式化輸出 |
九、知識結(jié)構(gòu)圖

十、工程原則
- 數(shù)據(jù)結(jié)構(gòu)先行:先確定 JSON 存儲結(jié)構(gòu),再寫代碼——改數(shù)據(jù)結(jié)構(gòu)代價很高,中途發(fā)現(xiàn)結(jié)構(gòu)不對再改會推翻很多代碼。
- 每個功能一個函數(shù):
add_todo、list_todos、complete_todo、delete_todo各司其職,主循環(huán)只負(fù)責(zé)分發(fā)任務(wù),不寫具體邏輯。 - 每次修改立即保存:這個工具的數(shù)據(jù)量極?。◣装贄l),每次操作后保存沒有性能問題,卻能保證程序崩潰時數(shù)據(jù)不丟失。
- 異常處理分散到各處,而非集中:
load_todos處理文件不存在,main的循環(huán)處理用戶輸入格式錯誤和強(qiáng)制退出——每個場景獨(dú)立處理,不需要一個大 try 包住整個程序。 - 用字典做分支映射:菜單選項 → 函數(shù)映射是一個值得養(yǎng)成的習(xí)慣——加新選項只需要在字典里加一行,主循環(huán)保持整潔。
到這里,從變量到函數(shù),從數(shù)據(jù)結(jié)構(gòu)到文件操作,從異常處理到模塊化設(shè)計——這一路走下來,已經(jīng)具備了獨(dú)立編寫小型 Python 程序的能力。
到此這篇關(guān)于使用Python編寫一個待辦事項管理器(CLI 版)的文章就介紹到這了,更多相關(guān)Python待辦事項管理器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Pandas?合并數(shù)據(jù)集merge?和?join的使用
Pandas?提供了一個重要的功能,即高性能的內(nèi)存中連接(join)和合并(merge)操作,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2026-06-06
啥是佩奇?使用Python自動繪畫小豬佩奇的代碼實(shí)例
今天小編就為大家分享一篇關(guān)于啥是佩奇?使用Python自動繪畫小豬佩奇的代碼實(shí)例,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-02-02
pygame+opencv實(shí)現(xiàn)讀取視頻幀的方法示例
由于pygame.movie.Movie.play()只支持MPEG格式的視頻,所以決定使用與opencv讀取視頻幀的畫面,本文就詳細(xì)的介紹了pygame+opencv實(shí)現(xiàn)讀取視頻幀,感興趣的可以了解一下2021-12-12
Python函數(shù)元數(shù)據(jù)實(shí)現(xiàn)為一個參數(shù)指定多個類型
這篇文章主要介紹了Python函數(shù)元數(shù)據(jù)實(shí)現(xiàn)為一個參數(shù)指定多個類型方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-02-02
django之狀態(tài)保持-使用redis存儲session的例子
今天小編就為大家分享一篇django之狀態(tài)保持-使用redis存儲session的例子,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-07-07

