3. 命令行入口与参数解析

命令行入口与参数解析 学习卡片

从模块到命令:console_scripts 的装配关系

前两例已经建立了 src/ 布局和锁定依赖的工程骨架。本例要让这个工程从"能被 import 的包"变成"能被终端直接调用的工具"。核心机制是 pyproject.toml 中的 [project.scripts] 表:它声明一个命令名,并把它绑定到某个可调用对象上。安装时,构建后端会在虚拟环境的 bin/(Windows 为 Scripts/)目录生成一个同名可执行脚本,该脚本导入目标函数并调用它。

console_scripts 入口装配与调用链 pyproject.toml [project.scripts] mytool = "mypkg.cli:main" 命令名 → 模块:函数 pip install -e . .venv/bin/mytool #!/usr/bin/python from mypkg.cli import main 自动生成的启动脚本 import src/mypkg/cli.py def main(): parser = argparse args = parser.parse_args() 子命令分发 + 参数校验 argparse 子命令与参数校验结构 主解析器 init --path (必填) run --verbose (开关) status 无额外参数 校验规则:init 缺少 --path → parser.error() 退出码 2 未知子命令 → 打印 usage 并退出码 2

在 pyproject.toml 中声明入口

打开工程根目录的 pyproject.toml,在 [project] 表下增加 scripts 子表。键是终端里敲的命令名,值是 "包名.模块名:函数名"。函数名指向 cli.py 中的 main,该函数不接受参数(或只接受可选参数),由它内部完成 argparse 的构建与解析。

[project]
name = "mypkg"
version = "0.1.0"
requires-python = ">=3.10"

[project.scripts]
mytool = "mypkg.cli:main"

[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"

用 argparse 实现子命令与校验

在 src/mypkg/cli.py 中实现 main()。关键点有三处:用 add_subparsers(dest="command") 建立子命令分发;对 init 的 --path 使用 required=True 强制校验;对未知子命令,argparse 会自动报错并以退出码 2 结束。为了让 main 可被 console_scripts 直接调用,它内部调用 parser.parse_args() 后根据 args.command 分发到具体处理函数。

import argparse
import sys
from pathlib import Path

def cmd_init(args):
    target = Path(args.path)
    target.mkdir(parents=True, exist_ok=True)
    print(f"initialized at {target.resolve()}")

def cmd_run(args):
    print(f"running (verbose={args.verbose})")

def cmd_status(args):
    print("status: ok")

def main(argv=None):
    parser = argparse.ArgumentParser(
        prog="mytool",
        description="mypkg 命令行工具",
    )
    sub = parser.add_subparsers(dest="command", required=True)

    p_init = sub.add_parser("init", help="初始化目录")
    p_init.add_argument("--path", required=True, help="目标路径")
    p_init.set_defaults(func=cmd_init)

    p_run = sub.add_parser("run", help="执行任务")
    p_run.add_argument("--verbose", action="store_true")
    p_run.set_defaults(func=cmd_run)

    p_status = sub.add_parser("status", help="查看状态")
    p_status.set_defaults(func=cmd_status)

    args = parser.parse_args(argv)
    args.func(args)

if __name__ == "__main__":
    main()

安装并验证全局调用

在已激活的虚拟环境中执行可编辑安装,使入口脚本立即生效:

pip install -e .
which mytool          # 应指向 .venv/bin/mytool
mytool --help         # 显示 prog=mytool 与三个子命令
mytool init --path ./demo
mytool run --verbose
mytool status

验证要点:which mytool 的输出必须落在当前虚拟环境的 bin/ 目录内,而不是系统路径;mytool --help 的 usage 行应以 mytool 开头,说明 prog 生效;执行 mytool init(不带 --path)应看到 argparse 报错并返回退出码 2,可用 echo $? 确认。若 which mytool 找不到命令,通常是虚拟环境未激活或安装时未使用 -e 导致入口未写入当前环境。

把 main(argv=None) 设计为接受可选参数列表,可以在测试中直接传入 ["init", "--path", "tmp"] 调用,无需启动子进程,便于后续为 CLI 编写单元测试。