← 返回 Day 30 正文

Day 30 · 图解

Day 030 — 阶段项目:命令行工具:图解

argparse 架构、CLI 工作流、数据流图


1️⃣ 命令行参数解析流程

用户输入
    │
    ▼
┌─────────────────────────────┐
│  shell 解析                  │
│  $ mytool subcommand --opt   │
└─────────────────────────────┘
    │
    ▼
┌─────────────────────────────┐
│  argparse 解析               │
│                             │
│  sys.argv = [mytool,        │
│    subcommand, --opt]       │
│                             │
│  subparsers → 路由子命令     │
│  add_argument → 绑定参数    │
└─────────────────────────────┘
    │
    ▼
┌─────────────────────────────┐
│  参数验证                    │
│  • 类型转换 (type=int)      │
│  • 值验证 (choices=[...])   │
│  • 文件验证 (FileType)      │
└─────────────────────────────┘
    │
    ▼
┌─────────────────────────────┐
│  业务逻辑处理                │
│  • args.command 分发        │
│  • 调用对应工具函数          │
│  • 输出结果                  │
└─────────────────────────────┘

2️⃣ 子命令架构(Git 风格)

mytools
  │
  ├── wc             文件统计
  │   ├── file                  位置参数
  │   ├── -l, --lines           只统计行数
  │   ├── -w, --words           只统计单词数
  │   └── -c, --chars           只统计字符数
  │
  ├── grep           文本搜索
  │   ├── pattern               搜索模式
  │   ├── file                  文件名
  │   ├── -i, --ignore-case     忽略大小写
  │   └── -n, --line-number     显示行号
  │
  ├── find           文件查找
  │   ├── root                  根目录
  │   ├── --name                文件名模式
  │   └── --type                类型 (f/d)
  │
  └── json-fmt       JSON 格式化
      ├── input                 JSON 字符串
      ├── --indent              缩进空格
      └── --sort                按键排序

Python 实现架构

python
# cli.py — 主入口
def main():
    parser = argparse.ArgumentParser()
    subparsers = parser.add_subparsers(dest='command')
    
    # 注册各个子命令
    register_wc(subparsers)
    register_grep(subparsers)
    register_find(subparsers)
    register_json_fmt(subparsers)
    
    args = parser.parse_args()
    
    # 路由到对应处理函数
    if args.command == 'wc':
        return wc_handler(args)
    elif args.command == 'grep':
        return grep_handler(args)
    # ...

3️⃣ 管道与重定向

stdin 重定向:
  $ cat data.txt | python mytool.py parse
                                    ↑
        ┌───────────────────────────┘
        ▼
    sys.stdin.read()
    
标准输出到文件:
  $ python mytool.py stats > report.txt
                              ↓
                         写入文件

管道组合:
  $ python mytool.py scan . | python mytool.py json-fmt
       ↑               ↑                   ↑
   扫描目录树     输出 JSON 列表       美化 JSON

Python 中检测管道输入

python
import sys

def has_pipe_input():
    """检测 stdin 是否来自管道"""
    return not sys.stdin.isatty()

if has_pipe_input():
    data = sys.stdin.read()
    # 处理管道输入
else:
    # 交互式模式
    pass

4️⃣ 工具集数据流

数据输入                  处理管道                 输出
─────────────────    ──────────────────    ─────────────
文件系统  ─────────→  FileInfoTool.get_info  ───────→ 终端文本
                      CodeStatsTool.count   ───────→ 表格/汇总
日志文件  ─────────→  LogAnalyzer.analyze   ───────→ 统计 + 图表
目录树    ─────────→  TreeTool.generate     ───────→ 树形文本
JSON 串   ─────────→  json_format_tool      ───────→ 格式化 JSON

5️⃣ argparse 参数类型对比

参数类型        语法示例                值类型          说明
────────────────────────────────────────────────────────────
positional     parser.add_argument     字符串          必须提供
               ('file')
optional       parser.add_argument     根据 type       用 -- 指定
               ('--output')
flag           parser.add_argument      bool           无需参数
               ('-v', action='store_true')
list           parser.add_argument      list           可重复
               ('-f', action='append')
choice         parser.add_argument      str            限定值范围
               ('--mode', choices=['a','b'])
count          parser.add_argument      int            计数
               ('-v', action='count')
file           parser.add_argument      file object    自动打开/关闭
               ('--log', type=FileType('w'))

6️⃣ 错误处理流程

用户输入错误
    │
    ▼
参数解析失败 ───→ argparse 自动生成错误信息
    │                 $ ./tool --unknown-opt
    │                 usage: tool [-h] ...
    │                 tool: error: unrecognized arguments: --unknown-opt
    │
    ▼
文件不存在 ──────→ 自定义错误处理
    │                 try:
    │                     open(file)
    │                 except FileNotFoundError:
    │                     print(f"错误: 文件 '{file}' 不存在", file=sys.stderr)
    │                     sys.exit(1)
    │
    ▼
业务逻辑错误 ────→ 自定义异常装饰器
    │                 @handle_error
    │                 def safe_main():
    │                     # ...
    │
    ▼
未捕获异常 ──────→ 全局异常处理
                    try:
                        main()
                    except Exception as e:
                        if '--debug' in sys.argv:
                            traceback.print_exc()
                        else:
                            print(f"错误: {e}", file=sys.stderr)
                        sys.exit(1)

7️⃣ Phase 2 学习路径总览

Phase 2: 核心编程概念 (Day 16–30)

Day 16-18: 数据结构基础
  ┌─────────────────────────────────────┐
  │ 列表 │ 元组 │ 集合 │ 字典            │
  └─────────────────────────────────────┘

Day 19: 字符串处理
  ┌─────────────────────────────────────┐
  │ 字符串方法 │ 正则 │ 格式化           │
  └─────────────────────────────────────┘

Day 20-23: 函数与函数式编程
  ┌─────────────────────────────────────┐
  │ 函数进阶 │ 迭代器 │ 生成器           │
  │ 装饰器   │ 高阶函数 │ lambda         │
  │ 映射/过滤/归约                       │
  └─────────────────────────────────────┘

Day 24-26: 进阶特性
  ┌─────────────────────────────────────┐
  │ 装饰器进阶 │ 上下文管理器            │
  │ 字符串进阶 │ datetime                │
  └─────────────────────────────────────┘

Day 27-29: 数据结构与算法
  ┌─────────────────────────────────────┐
  │ datetime │ 数据结构综合 │ 算法入门    │
  └─────────────────────────────────────┘

Day 30: 命令行工具 🏆
  ┌─────────────────────────────────────┐
  │ sys.argv │ argparse │ 文件工具       │
  │ 搜索工具 │ JSON工具 │ 输出格式化     │
  │ 安装打包 │ 综合项目                  │
  └─────────────────────────────────────┘

下一站: Phase 3 — 面向对象编程 🚀