← 回到手记
代码

argparse:别再手写 sys.argv 解析了

用标准库 argparse 声明命令行参数:自动帮助、类型与 choice 校验;别用 type=bool,开关用 store_true 或 BooleanOptionalAction。

要解决的问题

写 Python 后端或运维脚本时,总会碰到命令行入口:备份目录、跑批任务、起本地调试服务。很多人第一反应是直接抠 sys.argv[1],再自己写一堆 if 判断 -v、--output。结果是:缺参时没有友好报错、--help 要手写、类型转换(字符串变 int / Path)散落各处,测试时还得伪造整个 sys.argv。

标准库 argparse(自 Python 3.2)就是干这件事的:声明「需要哪些参数」,它负责从 sys.argv 里解析、自动生成帮助、发现非法参数时报错退出。本文示例已在本机 Python 3.13.5 跑通;核心 API 在 3.9+ 一致(文中会标出 3.9 / 3.13 才有的点)。

核心概念

以 Python 3.13 argparse 文档 为准,先抓住这几条:

  • ArgumentParser:整份命令行契约的容器。可设 prog、description、epilog;默认自带 -h/--help。
  • add_argument(...):声明位置参数(如 src)或可选参数(如 -o/--output)。常用关键字:type(转换)、choices(枚举)、default、required、help、nargs、action。
  • parse_args():解析后返回 argparse.Namespace,用属性读值(args.verbose)。可传入字符串列表,方便单测,不必改全局 sys.argv。
  • action:常见有 store(默认)、store_true / store_false、append、count。3.9+ 还有 BooleanOptionalAction,一次声明同时得到 --feature 与 --no-feature。
  • 非法参数默认退出:parse_args 遇到类型错误、非法 choice、缺必填项时,打印 usage 并以状态码 2 退出。若要自己捕获,可用 exit_on_error=False(3.9+) 再接 ArgumentError。

文档也写明:argparse 是标准库里做基础命令行应用的推荐默认;若你需要更极端的控制(例如完全禁止选项与位置参数交错),再去对照官方「Choosing an argument parsing library」。

示例代码

一个「备份」小入口:位置参数 + 必填输出路径 + 开关 + 带默认值的整数 + 枚举(本地已跑通):

import argparse
from pathlib import Path

parser = argparse.ArgumentParser(
    prog="backup",
    description="把目录打包成备份文件",
)
parser.add_argument("src", type=Path, help="源目录")
parser.add_argument("-o", "--output", type=Path, required=True, help="输出路径")
parser.add_argument("-v", "--verbose", action="store_true", help="打印细节")
parser.add_argument(
    "--keep",
    type=int,
    default=7,
    metavar="N",
    help="保留最近 N 份备份(默认:%(default)s)",
)
parser.add_argument(
    "--level",
    choices=["debug", "info", "warning"],
    default="info",
)

# 单测或 REPL 里传入列表,不必改 sys.argv
args = parser.parse_args(
    ["/tmp/data", "-o", "/tmp/out.tgz", "-v", "--keep", "3"]
)
assert args.src == Path("/tmp/data")
assert args.output == Path("/tmp/out.tgz")
assert args.verbose is True
assert args.keep == 3
assert args.level == "info"
# print(parser.format_help())  # 查看自动生成的帮助

type=Path、type=int 会在解析阶段转换;非法值(如 --keep abc)会立刻报错退出,不用业务代码再 try/int。help 里的 %(default)s 会填入默认值,和文档说明一致。

需要「同一选项出现多次」或「至少一个文件」时:

import argparse

p = argparse.ArgumentParser()
p.add_argument("--tag", action="append", default=[])
p.add_argument("files", nargs="+")
args = p.parse_args(["--tag", "a", "--tag", "b", "f1", "f2"])
assert args.tag == ["a", "b"]
assert args.files == ["f1", "f2"]

action="append" 每次追加到列表;nargs="+" 表示至少一个位置参数,缺了会报错。

布尔开关若希望正反两种写法(3.9+):

import argparse

p = argparse.ArgumentParser()
p.add_argument("--feature", action=argparse.BooleanOptionalAction, default=False)
assert p.parse_args(["--feature"]).feature is True
assert p.parse_args(["--no-feature"]).feature is False

常见误区

  1. 用 type=bool 解析布尔
    官方文档明确不推荐:bool("False") 在 Python 里是 True(非空字符串)。结果是命令行写了 False,程序仍当成开。请用 action="store_true",或 3.9+ 的 BooleanOptionalAction。

  2. 把本该是位置参数的东西硬做成 required=True 的可选参数
    文档写:required 选项通常被视为不良形式,因为用户默认认为「选项」可以省略。能写成位置参数就写位置参数;确需可选形式时再 required=True。

  3. 继续手拆 sys.argv,却又混用 argparse
    解析交给 parse_args;业务只读 Namespace。测试时传字符串列表即可。半自动半手写,边界情况(--、缩写、负数字符串)更容易踩坑。

  4. 在 type= 里做「打开文件 / 读 JSON」等重活
    文档提醒:type 适合简单转换(int、float、Path)。复杂错误(JSON 解码、文件找不到)用 type 往往报错不清晰,甚至像 FileType 在后续参数失败时可能来不及关掉已打开的文件。更稳妥是先解析出路径/字符串,再在下游用 with 打开、再 json.loads。

  5. 忘记非法参数会 sys.exit(2)
    默认 exit_on_error=True。若在库代码或需要自定义错误处理的场景调用 parse_args,要么包一层、要么设 exit_on_error=False(3.9+)自行捕获 argparse.ArgumentError。

小结

后端脚本、管理命令、本地工具入口,优先用标准库 argparse:声明参数、自动帮助、类型与 choice 校验,测试时传入列表即可。开关用 store_true / BooleanOptionalAction(3.9+),别用 type=bool;列表用 append / nargs;重逻辑放在解析之后。正式项目以你当前解释器版本的 argparse 文档 为准。

参考资料

  1. argparse — Parser for command-line options — Python 3.13 文档
  2. Argparse Tutorial — Python 文档
  3. argparse — 最新稳定版文档
✳

谢谢你读到这里。