Python 标准库 argparse 是编写命令行脚本时最常用的参数解析模块。相比直接读 sys.argv,它把参数定义、类型转换、默认值、帮助信息和错误提示集中到 ArgumentParser 上,适合参数较多的脚本。本文按实际使用顺序梳理 argparse 的核心对象和参数,并重点说明两个容易踩的坑:bool 参数错误使用 type=bool,以及反复调用 parser.parse_args() 带来的解析异常。
一、最小可用流程
argparse 的基本调用链是:导入模块,创建 ArgumentParser,调用 add_argument() 注册参数,再用 parse_args() 解析,最后通过返回对象的属性取值。示例:- import argparse
- def get_args():
- parser = argparse.ArgumentParser(description='示例 Python 脚本使用 argparse 解析命令行参数')
- parser.add_argument('name', type=str, help='你的名字')
- parser.add_argument('-a', '--age', type=int, default=20, help='你的年龄(可选,默认20)')
- parser.add_argument('-v', '--verbose', action='store_true', help='详细模式')
- args = parser.parse_args()
- print(f'你好, {args.name}!')
- print(f'你的年龄是: {args.age}')
- if args.verbose:
- print('详细模式已开启。')
- if __name__ == '__main__':
- get_args()
复制代码 运行 python example.py zhangsan -a 30 --verbose 时,位置参数 name 得到 zhangsan,-a 30 覆盖默认值 20,因此 age 为 30,verbose 为 True。这说明 argparse 会自动把字符串按 type=int 转成整数。
二、ArgumentParser 和帮助格式
创建解析器时可传入 description,用户执行 -h 或 --help 时显示说明。formatter_class 用于控制帮助信息格式,原文列出的四种为:argparse.RawDescriptionHelpFormatter、argparse.RawTextHelpFormatter、argparse.ArgumentDefaultsHelpFormatter、argparse.MetavarTypeHelpFormatter。实际常用 ArgumentDefaultsHelpFormatter,它会把默认值自动追加到每个参数的帮助文本中。例如:- parser = argparse.ArgumentParser(
- description='示例 Python 脚本使用 argparse 解析命令行参数',
- formatter_class=argparse.ArgumentDefaultsHelpFormatter
- )
复制代码
三、add_argument 的关键参数
add_argument() 的签名包含 name or flags、action、nargs、const、default、type、choices、required、help、metavar、dest。位置参数直接写名称,是必填项;可选参数以 - 或 -- 开头,不传时使用 default。type 指定转换函数,常见为 int、float、str;choices 限定可选值;required=True 可让可选参数变成必填。dest 决定解析结果存到 Namespace 的哪个属性名。可选参数默认从第一个长选项去掉 -- 生成 dest,没有长选项时从第一个短选项去掉 - 生成,内部 - 会转成 _。例如:- parser = argparse.ArgumentParser()
- parser.add_argument('-f', '--foo-bar', '--foo')
- parser.add_argument('-x', '-y')
- print(parser.parse_args('-f 1 -x 2'.split()))
- # Namespace(foo_bar='1', x='2')
- parser = argparse.ArgumentParser()
- parser.add_argument('--foo', dest='bar')
- print(parser.parse_args('--foo XXX'.split()))
- # Namespace(bar='XXX')
复制代码
四、action 与 nargs 的常见写法
action 决定参数如何处理。默认是 store,把值存入 Namespace;store_const 存入 const 指定的常量;store_true 和 store_false 是 store_const 的特殊形式,常用于布尔开关;append 会把多次出现的值收集成列表。示例:- parser = argparse.ArgumentParser()
- parser.add_argument('--foo', action='append')
- print(parser.parse_args('--foo 1 --foo 2'.split()))
- # Namespace(foo=['1', '2'])
复制代码 nargs 控制参数个数。N 表示固定数量并收集为列表;? 表示零或一个,没有值时用 const,缺省时用 default;* 表示零或多个;+ 表示一个或多个,少于一个会报错。例如:- parser = argparse.ArgumentParser()
- parser.add_argument('--range', nargs=2)
- parser.add_argument('bar', nargs=1)
- print(parser.parse_args('c --range 1 10'.split()))
- # Namespace(bar=['c'], range=['1', '10'])
复制代码 注意 nargs=1 得到的是单元素列表,和默认的直接取值不同。
五、parse_args、Namespace 与 Action
parse_args(args=None, namespace=None) 返回 Namespace。args 默认从 sys.argv 读取,namespace 可传入已有对象。解析后通过点号访问属性,例如 args.name、args.age。Namespace 本质是简单容器,可以手动创建、动态增删改属性,也能用 vars() 转成字典:- from argparse import Namespace
- args = Namespace(name='Bob', age=30)
- print(args.name, args.age)
- data = {'name': 'Charlie', 'age': 28}
- args = Namespace(**data)
- args.age = 35
- del args.name
- print(vars(args))
复制代码 Action 对象是 parser 内部描述单个参数的载体。parser._actions 列表包含所有 add_argument() 注册的参数信息,遍历后可通过 action.dest 和 action.default 取得存储名和默认值:- inference_args_dict = {}
- for action in parser._actions:
- inference_args_dict[action.dest] = action.default
复制代码
六、坑一:bool 参数不能直接 type=bool
下面写法是典型错误:- parser.add_argument('--is_test', type=bool, default=False, help='是否使用测试模式')
复制代码 原因是 bool('False')、bool('0')、bool('no') 都是 True,Python 中非空字符串的布尔值就是 True。因此不管命令行传什么字符,args.is_test 都可能变成 True。正确做法是自定义转换函数:- def str2bool(v):
- if isinstance(v, bool):
- return v
- if v.lower() in ('yes', 'true', 't', 'y', '1'):
- return True
- elif v.lower() in ('no', 'false', 'f', 'n', '0'):
- return False
- else:
- raise argparse.ArgumentTypeError('Boolean value expected.')
- parser.add_argument('--is_test', type=str2bool, default=False, help='是否使用测试模式')
复制代码 如果只是需要一个不加值就为 True 的开关,用 action='store_true' 更直接;如果需要显式传入 true/false,则使用 str2bool。
七、坑二:多次调用 parser.parse_args()
在程序多个位置重复调用 parser.parse_args(),可能重新读取 sys.argv,造成参数冲突、重复处理或意外行为。更稳妥的方式是整个程序只调用一次 parse_args()。如果还需要另一组默认配置,可以单独创建 parser,遍历 _actions 取默认值,再转成 Namespace,而不是再次解析命令行:- def init_args():
- parser = argparse.ArgumentParser()
- parser.add_argument('--gpu_mem', type=int, default=500)
- parser.add_argument('--gpu_id', type=int, default=0)
- return parser
- def get_args():
- parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
- parser.add_argument('--is_test', type=str2bool, default=False, help='是否使用测试模式')
- return parser.parse_args()
- def main():
- parser = init_args()
- inference_args_dict = {}
- for action in parser._actions:
- inference_args_dict[action.dest] = action.default
- args1 = argparse.Namespace(**inference_args_dict)
- args = get_args()
复制代码
总结:argparse 适合复杂命令行接口,能把帮助、默认值、类型转换和校验集中处理。使用时注意位置参数与可选参数的区别,理解 dest、action、nargs 的行为,尤其是 bool 参数不要直接 type=bool,parse_args 也不要重复调用。掌握这些点后,命令行脚本的参数处理会更稳定。 |