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