配套专栏:Python 全栈修炼之路 第 12 篇《阶段实战——命令行工具开发》
难度分布:⭐ → ⭐⭐ → ⭐⭐ → ⭐⭐⭐ → ⭐⭐⭐ → ⭐⭐⭐⭐
核心覆盖:argparse、@dataclass、JSON 持久化、rich 美化输出、模块化设计、单元测试
前言
第十二篇综合运用了 Python 基础阶段的所有知识:数据类型、函数、文件操作、异常处理、模块组织、面向对象等。本练习从零开始,通过 6 个由易到难的项目,帮助你掌握从简单列表管理到完整 CLI 工具开发的全部技能。
题目一:文本版任务管理器 ⭐
📌 题目描述
用纯 Python 实现一个最简单的任务管理器,数据保存在内存中(列表),支持增删查改:
tm = TaskManager()
tm.add(\”完成Python项目\”, priority=\”high\”)
tm.add(\”阅读技术文档\”, priority=\”medium\”)
tm.add(\”回复邮件\”, priority=\”low\”)
tm.list()
# 输出:
# 1. [高] 完成Python项目
# 2. [中] 阅读技术文档
# 3. [低] 回复邮件
tm.done(1)
tm.list()
# 1. [中] 阅读技术文档
# 2. [低] 回复邮件
# 3. [高✓] 完成Python项目
tm.delete(2)
tm.list()
# 1. [低] 回复邮件
# 2. [高✓] 完成Python项目
💡 编程思路
这道题考察 基础数据结构(列表/字典)+ 函数封装 + 类设计:
🖥️ 参考代码
from typing import Optional
class TaskManager:
\”\”\”纯内存版任务管理器。\”\”\”
PRIORITY_MAP = {
\”high\”: \”高\”,
\”medium\”: \”中\”,
\”low\”: \”低\”,
}
def __init__(self):
self.tasks: list[dict] = []
def add(self, title: str, priority: str = \”medium\”) –> None:
\”\”\”添加任务。\”\”\”
if priority not in self.PRIORITY_MAP:
raise ValueError(f\”无效优先级:{
priority},可选值: {
list(self.PRIORITY_MAP.keys())}\”)
task = {
\”title\”: title,
\”priority\”: priority,
\”done\”: False,
}
self.tasks.append(task)
print(f\”✓ 添加任务:{
title}\”)
def done(self, index: int) –> None:
\”\”\”标记任务完成(index 从 1 开始)。\”\”\”
if not 1 <= index <= len(self.tasks):
raise IndexError(f\”任务编号 {
index} 不存在\”)
self.tasks[index – 1][\”done\”] = True
print(f\”✓ 完成任务:{
self.tasks[index – 1][\’title\’]}\”)
def delete(self, index: int) –> None:
\”\”\”删除任务(index 从 1 开始)。\”\”\”
if not 1 <= index <= len(self.tasks):
raise IndexError(f\”任务编号 {
index} 不存在\”)
removed = self.tasks.pop(index – 1)
print(f\”✓ 删除任务:{
removed[\’title\’]}\”)
def list(self) –> None:
\”\”\”列出所有任务。\”\”\”
if not self.tasks:
print(\”暂无任务\”)
return
for i, task in enumerate(self.tasks, 1):
status = \”✓\” if task[\”done\”] else \”\”
priority_label = self.PRIORITY_MAP[task[\”priority\”]]
priority_str = f\”[{
priority_label}{
status}]\”
print(f\”{
i}. {
priority_str} {
task[\’title\’]}\”)
def find(self, keyword: str) –> list[tuple[int, dict]]:
\”\”\”搜索包含关键词的任务。\”\”\”
results = []
for i, task in enumerate(self.tasks, 1):
if keyword.lower() in task[\”title\”].lower():
results.append((i, task))
return results
# 测试
if __name__ == \”__main__\”:
tm = TaskManager()
print(\”=== 添加任务 ===\”)
tm.add(\”完成Python项目\”, priority=\”high\”)
tm.add(\”阅读技术文档\”, priority=\”medium\”)
tm.add(\”回复邮件\”, priority=\”low\”)
print(\”\\n=== 任务列表 ===\”)
tm.list()
print(\”\\n=== 搜索 ===\”)
for idx, task in tm.find(\”文档\”):
print(f\” #{
idx}: {
task[\’title\’]}\”)
print(\”\\n=== 完成任务 ===\”)
tm.done(1)
tm.list()
print(\”\\n=== 删除任务 ===\”)
tm.delete(2)
tm.list()
print(\”\\n所有测试通过 ✓\”)
🔗 关联知识点
| list.append() | 添加任务到列表 |
| list.pop() | 删除指定位置元素 |
| enumerate(…, 1) | 带序号的遍历 |
| 字典映射 | 优先级中英文转换 |
| 函数封装 | 类方法设计 |
题目二:命令行版任务管理器(argparse) ⭐⭐
📌 题目描述
使用 argparse 将任务管理器改造为真正的命令行工具:
$ python task_cli.py add \”完成项目文档\” –priority high
✓ 任务已添加
$ python task_cli.py list
1. [高] 完成项目文档
$ python task_cli.py done 1
✓ 任务已完成
$ python task_cli.py delete 1 –force
✓ 任务已删除
$ python task_cli.py –help
usage: task_cli.py [-h] {
add,list,done,delete} ...
任务管理器 CLI
...
💡 编程思路
这道题考察 argparse 子命令解析 + CLI 程序结构:
🖥️ 参考代码
\”\”\”命令行版任务管理器(使用 argparse)。\”\”\”
import argparse
import json
import os
from pathlib import Path
from typing import Optional
class TaskItem:
\”\”\”任务数据类(用普通类模拟 dataclass)。\”\”\”
def __init__(self, title: str, priority: str = \”medium\”, done: bool = False):
self.title = title
self.priority = priority
self.done = done
def to_dict(self) –> dict:
return {
\”title\”: self.title, \”priority\”: self.priority, \”done\”: self.done}
@classmethod
def from_dict(cls, data: dict) –> \”TaskItem\”:
return cls(data[\”title\”], data.get(\”priority\”, \”medium\”), data.get(\”done\”, False))
class TaskCLI:
\”\”\”任务管理 CLI。\”\”\”
DATA_FILE = Path.home() / \”.taskcli\” / \”tasks.json\”
def __init__(self):
self.tasks: list[TaskItem] = []
self._ensure_data_dir()
self._load()
def _ensure_data_dir(self):
self.DATA_FILE.parent.mkdir(parents=True, exist_ok=True)
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.tasks = [TaskItem.from_dict(t) for t in data]
except (json.JSONDecodeError, KeyError):
self.tasks = []
def _save(self):
with open(self.DATA_FILE, \”w\”, encoding=\”utf-8\”) as f:
json.dump([t.to_dict() for t in self.tasks], f, ensure_ascii=False, indent=2)
# === 命令处理方法 ===
def cmd_add(self, args):
task = TaskItem(title=args.title, priority=args.priority)
self.tasks.append(task)
self._save()
print(f\”✓ 任务已添加(当前共 {
len(self.tasks)} 个任务)\”)
def cmd_list(self, args):
if not self.tasks:
print(\”暂无任务\”)
return
for i, task in enumerate(self.tasks, 1):
mark = \”✓\” if task.done else \” \”
priority_map = {
\”high\”: \”高\”, \”medium\”: \”中\”, \”low\”: \”低\”}
pri = priority_map[task.priority]
status = f\”[{
pri}]\” if not task.done else f\”[{
pri}✓]\”
print(f\”{
i}. {
status} {
task.title}\”)
# 统计
done_count = sum(1 for t in self.tasks if t.done)
print(f\”\\n共 {
len(self.tasks)} 个任务,已完成 {
done_count} 个\”)
def cmd_done(self, args):
try:
idx = int(args.index) – 1
if not 0 <= idx < len(self.tasks):
raise IndexError
self.tasks[idx].done = True
self._save()
print(f\”✓ 任务已完成:{
self.tasks[idx].title}\”)
except (ValueError, IndexError):
print(f\”✗ 任务编号 {
args.index} 不存在\”)
def cmd_delete(self, args):
if not args.force:
confirm = input(f\”确认删除任务 {
args.index}?[y/N]: \”)
if confirm.lower() != \”y\”:
print(\”已取消\”)
return
try:
idx = int(args.index) – 1
removed = self.tasks.pop(idx)
self._save()
print(f\”✓ 任务已删除:{
removed.title}\”)
except (ValueError, IndexError):
print(f\”✗ 任务编号 {
args.index} 不存在\”)
def cmd_clear(self, args):
if not args.force:
confirm = input(f\”确认清空所有 {
len(self.tasks)} 个任务?[y/N]: \”)
if confirm.lower() != \”y\”:
print(\”已取消\”)
return
self.tasks.clear()
self._save()
print(\”✓ 所有任务已清空\”)
def run(self, args=None):
\”\”\”运行 CLI。\”\”\”
parser = self._create_parser()
parsed = parser.parse_args(args)
if not parsed.command:
parser.print_help()
return
cmd_method = getattr(self, f\”cmd_{
parsed.command}\”, None)
if cmd_method:
cmd_method(parsed)
else:
print(f\”✗ 未知命令:{
parsed.command}\”)
def _create_parser(self) –> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
prog=\”taskcli\”,
description=\”任务管理器 CLI\”,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
sub = parser.add_subparsers(dest=\”command\”, help=\”可用命令\”)
# add
p_add = sub.add_parser(\”add\”, help=\”添加新任务\”)
p_add.add_argument(\”title\”, help=\”任务标题\”)
p_add.add_argument(\”-p\”, \”–priority\”, choices=[\”high\”, \”medium\”, \”low\”],
default=\”medium\”, help=\”优先级(默认 medium)\”)
# list
sub.add_parser(\”list\”, help=\”列出所有任务\”)
# done
p_done = sub.add_parser(\”done\”, help=\”标记任务完成\”)
p_done.add_argument(\”index\”, help=\”任务编号\”)
# delete
p_del = sub.add_parser(\”delete\”, help=\”删除任务\”)
p_del.add_argument(\”index\”, help=\”任务编号\”)
p_del.add_argument(\”-f\”, \”–force\”, action=\”store_true\”, help=\”跳过确认\”)
# clear
p_clear = sub.add_parser(\”clear\”, help=\”清空所有任务\”)
p_clear.add_argument(\”-f\”, \”–force\”, action=\”store_true\”, help=\”跳过确认\”)
return parser
def main():
cli = TaskCLI()
cli.run()
if __name__ == \”__main__\”:
main()
🔗 关联知识点
| argparse.ArgumentParser | 命令行参数解析器 |
| add_subparsers() | 子命令模式 |
| add_argument() | 定义参数(位置/可选) |
| getattr(dynamic) | 动态方法调用 |
| JSON 文件持久化 | _save() / _load() |
题目三:任务模型(dataclass + Enum + TypeHint) ⭐⭐
📌 题目描述
使用 @dataclass、Enum 和类型提示重构任务模型:
from datetime import date, timedelta
# 创建任务
task = Task(
title=\”完成项目文档\”,
priority=Priority.HIGH,
due_date=date.today() + timedelta(days=7)
)
print(task)
# Task: 完成项目文档 [高] 剩余7天
# 逾期检测
task.due_date = date.today() – timedelta(days=1)
print(task.is_overdue()) # True
print(task) # Task: 完成项目文档 [高] 已逾期!
# 数据序列化
data = task.to_dict()
print(data)
# {\’title\’: \’完成项目文档\’, \’priority\’: \’HIGH\’, \’due_date\’: \’…\’, …}
# 比较和哈希
task2 = Task(title=\”完成项目文档\”, priority=Priority.HIGH)
print(task == task2) # True(相同标题+优先级视为相等)
💡 编程思路
这道题考察 @dataclass + Enum + @property + 类型提示:
🖥️ 参考代码
from dataclasses import dataclass, field
from datetime import date, datetime
from enum import Enum, auto
from typing import Optional
import uuid
class Priority(Enum):
\”\”\”任务优先级枚举。\”\”\”
LOW = auto()
MEDIUM = auto()
HIGH = auto()
@property
def label(self) –> str:
return {
Priority.LOW: \”低\”, Priority.MEDIUM: \”中\”, Priority.HIGH: \”高\”}[self]
@property
def weight(self) –> int:
\”\”\”优先级权重(数值越大越重要)。\”\”\”
return {
Priority.LOW: 1, Priority.MEDIUM: 2, Priority.HIGH: 3}[self]
class Status(Enum):
\”\”\”任务状态枚举。\”\”\”
TODO = auto()
IN_PROGRESS = auto()
DONE = auto()
@property
def label(self) –> str:
return {
Status.TODO: \”待办\”, Status.IN_PROGRESS: \”进行中\”, Status.DONE: \”已完成\”}[self]
@dataclass
class Task:
\”\”\”任务数据类。\”\”\”
title: str
priority: Priority = Priority.MEDIUM
status: Status = Status.TODO
description: str = \”\”
due_date: Optional[date] = None
tags: list[str] = field(default_factory=list)
# 自动生成字段
id: str = field(default_factory=lambda: str(uuid.<


