Python argparse同时使用nargs='*'、choices和default的问题
问题场景
需要实现多个可选位置参数,取值必须限定在指定的choices集合内,默认值为集合中两个选项组成的列表。同时要兼容Python 3.4,且因向后兼容要求不能修改现有接口。
原尝试代码
我最初编写的代码如下,期望默认值为['a', 'd']:
parser = argparse.ArgumentParser() parser.add_argument('tests', nargs='*', choices=['a', 'b', 'c', 'd'], default=['a', 'd']) args = parser.parse_args() print(args.tests)
符合预期的运行结果
以下几种执行场景的输出均符合预期:
$ ./test.py a ['a'] $ ./test.py a d ['a', 'd'] $ ./test.py a e usage: test.py [-h] [{a,b,c,d} ...] test.py: error: argument tests: invalid choice: 'e' (choose from 'a', 'b', 'c', 'd')
不符合预期的情况
但直接执行脚本不传入任何参数时,报错结果不符合预期:
$ ./test.py usage: test.py [-h] [{a,b,c,d} ...] test.py: error: argument tests: invalid choice: ['a', 'd'] (choose from 'a', 'b', 'c', 'd')
方案探索
我查找了大量类似问题,但均未覆盖该特定场景。找到的可行方案是编写自定义action替代原生choices逻辑。
一开始我抗拒自定义方案,认为这类场景应该有内置解决方式,于是考虑过临时方案:
parser.add_argument('tests', nargs='*', choices=['a', 'b', 'c', 'd', 'default'], default='default')
该方案允许新增参数,且能保持向后兼容。
最终实现
最后还是采用了自定义action方案。虽然一开始抵触,但发现子类化argparse.Action本就是应对这类场景的设计,它能清晰表达逻辑,还能提供最简洁的交互结果。
自定义action代码如下:
class TestsArgAction(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): all_tests = ['a', 'b', 'c', 'd'] default_tests = ['a', 'd'] if not values: setattr(namespace, self.dest, default_tests) return # 无参数时默认值会被当成字符串'default'而非列表['default'],这里统一转成列表 if not isinstance(values, list): values = [values] tests = set(values) # 处理'all'参数:替换为指定的全部测试项(因兼容原因不包含测试'e') try: tests.remove('all') tests.update(set(all_tests)) except KeyError: pass # 处理'default'参数:替换为默认测试项 try: tests.remove('default') tests.update(set(default_tests)) except KeyError: pass setattr(namespace, self.dest, sorted(list(tests)))
内容的提问来源于stack exchange,提问作者narwahl
相关产品推荐
相关产品推荐

