查看: 209|回复: 0

Python argparse参数解析与bool类型常见坑

[复制链接]
发表于 1 小时前 | 显示全部楼层 |阅读模式
Python 标准库 argparse 是编写命令行脚本时最常用的参数解析模块。相比直接读 sys.argv,它把参数定义、类型转换、默认值、帮助信息和错误提示集中到 ArgumentParser 上,适合参数较多的脚本。本文按实际使用顺序梳理 argparse 的核心对象和参数,并重点说明两个容易踩的坑:bool 参数错误使用 type=bool,以及反复调用 parser.parse_args() 带来的解析异常。

一、最小可用流程

argparse 的基本调用链是:导入模块,创建 ArgumentParser,调用 add_argument() 注册参数,再用 parse_args() 解析,最后通过返回对象的属性取值。示例:
  1. import argparse
  2. def get_args():
  3.     parser = argparse.ArgumentParser(description='示例 Python 脚本使用 argparse 解析命令行参数')
  4.     parser.add_argument('name', type=str, help='你的名字')
  5.     parser.add_argument('-a', '--age', type=int, default=20, help='你的年龄(可选,默认20)')
  6.     parser.add_argument('-v', '--verbose', action='store_true', help='详细模式')
  7.     args = parser.parse_args()
  8.     print(f'你好, {args.name}!')
  9.     print(f'你的年龄是: {args.age}')
  10.     if args.verbose:
  11.         print('详细模式已开启。')
  12. if __name__ == '__main__':
  13.     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,它会把默认值自动追加到每个参数的帮助文本中。例如:
  1. parser = argparse.ArgumentParser(
  2.     description='示例 Python 脚本使用 argparse 解析命令行参数',
  3.     formatter_class=argparse.ArgumentDefaultsHelpFormatter
  4. )
复制代码

三、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,没有长选项时从第一个短选项去掉 - 生成,内部 - 会转成 _。例如:
  1. parser = argparse.ArgumentParser()
  2. parser.add_argument('-f', '--foo-bar', '--foo')
  3. parser.add_argument('-x', '-y')
  4. print(parser.parse_args('-f 1 -x 2'.split()))
  5. # Namespace(foo_bar='1', x='2')
  6. parser = argparse.ArgumentParser()
  7. parser.add_argument('--foo', dest='bar')
  8. print(parser.parse_args('--foo XXX'.split()))
  9. # Namespace(bar='XXX')
复制代码

四、action 与 nargs 的常见写法

action 决定参数如何处理。默认是 store,把值存入 Namespace;store_const 存入 const 指定的常量;store_true 和 store_false 是 store_const 的特殊形式,常用于布尔开关;append 会把多次出现的值收集成列表。示例:
  1. parser = argparse.ArgumentParser()
  2. parser.add_argument('--foo', action='append')
  3. print(parser.parse_args('--foo 1 --foo 2'.split()))
  4. # Namespace(foo=['1', '2'])
复制代码
nargs 控制参数个数。N 表示固定数量并收集为列表;? 表示零或一个,没有值时用 const,缺省时用 default;* 表示零或多个;+ 表示一个或多个,少于一个会报错。例如:
  1. parser = argparse.ArgumentParser()
  2. parser.add_argument('--range', nargs=2)
  3. parser.add_argument('bar', nargs=1)
  4. print(parser.parse_args('c --range 1 10'.split()))
  5. # 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() 转成字典:
  1. from argparse import Namespace
  2. args = Namespace(name='Bob', age=30)
  3. print(args.name, args.age)
  4. data = {'name': 'Charlie', 'age': 28}
  5. args = Namespace(**data)
  6. args.age = 35
  7. del args.name
  8. print(vars(args))
复制代码
Action 对象是 parser 内部描述单个参数的载体。parser._actions 列表包含所有 add_argument() 注册的参数信息,遍历后可通过 action.dest 和 action.default 取得存储名和默认值:
  1. inference_args_dict = {}
  2. for action in parser._actions:
  3.     inference_args_dict[action.dest] = action.default
复制代码

六、坑一:bool 参数不能直接 type=bool

下面写法是典型错误:
  1. parser.add_argument('--is_test', type=bool, default=False, help='是否使用测试模式')
复制代码
原因是 bool('False')、bool('0')、bool('no') 都是 True,Python 中非空字符串的布尔值就是 True。因此不管命令行传什么字符,args.is_test 都可能变成 True。正确做法是自定义转换函数:
  1. def str2bool(v):
  2.     if isinstance(v, bool):
  3.         return v
  4.     if v.lower() in ('yes', 'true', 't', 'y', '1'):
  5.         return True
  6.     elif v.lower() in ('no', 'false', 'f', 'n', '0'):
  7.         return False
  8.     else:
  9.         raise argparse.ArgumentTypeError('Boolean value expected.')
  10. 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,而不是再次解析命令行:
  1. def init_args():
  2.     parser = argparse.ArgumentParser()
  3.     parser.add_argument('--gpu_mem', type=int, default=500)
  4.     parser.add_argument('--gpu_id', type=int, default=0)
  5.     return parser
  6. def get_args():
  7.     parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
  8.     parser.add_argument('--is_test', type=str2bool, default=False, help='是否使用测试模式')
  9.     return parser.parse_args()
  10. def main():
  11.     parser = init_args()
  12.     inference_args_dict = {}
  13.     for action in parser._actions:
  14.         inference_args_dict[action.dest] = action.default
  15.     args1 = argparse.Namespace(**inference_args_dict)
  16.     args = get_args()
复制代码

总结:argparse 适合复杂命令行接口,能把帮助、默认值、类型转换和校验集中处理。使用时注意位置参数与可选参数的区别,理解 dest、action、nargs 的行为,尤其是 bool 参数不要直接 type=bool,parse_args 也不要重复调用。掌握这些点后,命令行脚本的参数处理会更稳定。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-9-22 13:29 , Processed in 0.019698 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部