使用argparse处理组之间的Python依赖关系
我开始学习Python,现在我正在了解使用argparse
的好处。通过argparse
,我创建了两个参数组:group_list
和group_simulate
。每个组都有自己的参数——用户在每个组中只能指定一个参数(这是通过parser.add_mutually_exclusive_group()
实现的)。
现在我的目标是,如果用户同时从两个组中指定了参数,而不是只从其中一个组中指定参数,就要显示语法错误——我想通过argparse
的功能来实现这一点,而不是写一个方法去检查是否指定了这些参数,然后打印语法错误。
import argparse
parser = argparse.ArgumentParser(
description='this is the description',
epilog="This is the epilog",
argument_default=argparse.SUPPRESS
)
parser.add_argument('-v', '--verbose', help='verbose', action='store_true', default=False)
group_list = parser.add_mutually_exclusive_group()
group_list.add_argument('-m', help='list only modules', action='store_const', dest='list', const='modules', default='all')
group_list.add_argument('-p', help='list only ports', action='store_const', dest='list', const='ports', default='all')
group_list.add_argument('--list', help='list only module or ports', choices=['modules','ports'], metavar='<modules/ports>', default='all')
group_simulate = parser.add_mutually_exclusive_group()
group_simulate.add_argument('-M', help='simulate module down', nargs=1, metavar='module_name', dest='simulate')
group_simulate.add_argument('-P', help='simulate FC port down', nargs=1, metavar='fc_port_name', dest='simulate')
group_simulate.add_argument('-I', help='simulate iSCSI port down', nargs=1, metavar='iSCSI_port_name', dest='simulate')
group_simulate.add_argument('--simulate', help='simulate module or port down', nargs=1, dest='simulate')
args = parser.parse_args()
print args
具体来说:
允许的情况:
test.py
output: Namespace(list='all', verbose=False)
test.py -m
output: Namespace(list='modules', verbose=False)
test.py -P asfasf
output: Namespace(P=['asfasf'], list='all', verbose=False)
不允许的情况:
test.py -m -P asfsaf
expected output: <the help message>
test.py -P asfasf -m
expected output: <the help message>
我尝试使用argparse
中的add_subparsers
选项来实现这个目标,但没有成功。
所以我的问题是,如何实现这种情况呢?
3 个回答
这个解析器的简单版本是:
parser=argparse.ArgumentParser(description="this is the description",
epilog='this is the epilog')
parser.add_argument('-v', '--vebose', action='count')
g1=parser.add_mutually_exclusive_group()
g1.add_argument('--list', help='list module or ports (default=%(default)s)', choices=['modules','ports','all'], default='all')
g1.add_argument('--simulate', '-M','-P','-C', help='simulate [module down/ FS port down/ iSCSI port down]', dest='simulate', metavar='module/port')
它的帮助信息看起来像这样:
usage: stack14660876.py [-h] [-v]
[--list {modules,ports,all} | --simulate module/port]
this is the description
optional arguments:
-h, --help show this help message and exit
-v, --vebose
--list {modules,ports,all}
list module or ports (default=all)
--simulate module/port, -M module/port, -P module/port, -C module/port
simulate [module down/ FS port down/ iSCSI port down]
this is the epilog
除了 verbose
(这里我用 count
替代了它),原作者还设置了属性 list
和 simulate
。list
的默认值是 all
,也可以设置为 modules
或 ports
。-m
和 -p
只是快捷方式,并没有真正增加定义的内容。当定义很多选项时,快捷方式会很方便,尤其是这些选项可以一起使用(例如 -vpm
)。不过在这里,除了 -v
之外,只允许一个选项。
simulate
接受一个没有限制的字符串。M/P/C
选项只是为了方便文档,并没有限制值或增加意义。
这是一个很好的练习,可以推动 argparse
(或其他解析器)的边界,但我觉得它太复杂了,实用性不高。尽管有很多分组,但最终只允许一个选项。
==========================
关于 docopt
和 POSIX
参数处理的评论让我去看看 C 语言的参数库。getopt
是老标准。Python 有一个功能相似的库,链接在这里:https://docs.python.org/2/library/getopt.html
GNU 库中的另一个解析器是 argp
。
http://www.gnu.org/software/libc/manual/html_node/Argp.html
我还没有看到关于它对 getopt
语法的具体描述。但下面这段话很有趣。
Argp 还提供了将几个独立定义的选项解析器合并为一个的能力,调解它们之间的冲突,使结果看起来无缝。一个库可以导出一个 argp 选项解析器,用户程序可以与自己的选项解析器一起使用,从而减少用户程序的工作量。有些程序可能只使用库导出的参数解析器,从而实现库实现的抽象的一致和高效的选项解析。
这听起来有点像 argparse
的子解析器机制。也就是说,有某种元解析器可以将操作委托给一个(或多个)子解析器。但在 argparse
中,子解析器必须由用户明确命名。
一个可能的扩展是让元解析器查看上下文。例如在原作者的案例中,如果它看到任何 [--list, -p, -m]
,就使用 list
子解析器;如果看到任何 simulate
参数,就使用 simulate
子解析器。这可能会提供更强大的分组工具。而且可能可以用标准的 argparse
实现这种功能。你可以在同一个 sys.argv
上创建并运行多个不同的 parsers
。
使用Docopt吧!你不需要先写一个使用说明文档,然后花几个小时去搞明白怎么让argparse为你生成它。如果你了解POSIX标准,你就知道怎么理解使用说明文档,因为这是一个标准。Docopt也能像你一样理解这些使用说明文档。我们不需要一个抽象层。
我觉得提问者没有很好地描述他们的意图,基于我看到的帮助文本,我来猜测一下他们想做什么。
test.py
"""
usage: test.py [-h | --version]
test.py [-v] (-m | -p)
test.py [-v] --list (modules | ports)
test.py [-v] (-M <module_name> | -P <fc_port_name> | -I <iSCSI_port_name>)
this is the description
optional arguments:
-h, --help show this help message and exit
-v, --verbose verbose
-m list only modules (same as --list modules)
-p list only ports (same as --list ports)
--list list only module or ports
-M module_name simulate module down
-P fc_port_name simulate FC port down
-I iSCSI_port_name simulate iSCSI port down
This is the epilog
"""
from pprint import pprint
from docopt import docopt
def cli():
arguments = docopt(__doc__, version='Super Tool 0.2')
pprint(arguments)
if __name__ == '__main__':
cli()
虽然可以用复杂的嵌套条件在一行里传达所有用法,但这样会让人看得更费劲。这就是为什么使用docopt是个好主意。对于一个命令行程序,你要确保能清楚地传达给用户。为什么要学习一些晦涩的模块语法,希望它能为你生成用户沟通的内容呢?花点时间看看其他符合你需求的POSIX工具的选项规则,然后直接复制粘贴就好了。
你可以把一个常见的互斥组当作两个子组的“根”:
import argparse
parser = argparse.ArgumentParser(
description='this is the description',
epilog="This is the epilog",
argument_default=argparse.SUPPRESS
)
parser.add_argument('-v', '--verbose', help='verbose', action='store_true', default=False)
root_group = parser.add_mutually_exclusive_group()
group_list = root_group.add_mutually_exclusive_group()
group_list.add_argument('-m', help='list only modules', action='store_const', dest='list', const='modules', default='all')
group_list.add_argument('-p', help='list only ports', action='store_const', dest='list', const='ports', default='all')
group_list.add_argument('--list', help='list only module or ports', choices=['modules','ports'], metavar='<modules/ports>', default='all')
group_simulate = root_group.add_mutually_exclusive_group()
group_simulate.add_argument('-M', help='simulate module down', nargs=1, metavar='module_name', dest='simulate')
group_simulate.add_argument('-P', help='simulate FC port down', nargs=1, metavar='fc_port_name', dest='simulate')
group_simulate.add_argument('-I', help='simulate iSCSI port down', nargs=1, metavar='iSCSI_port_name', dest='simulate')
group_simulate.add_argument('--simulate', help='simulate module or port down', nargs=1, dest='simulate')
args = parser.parse_args()
print args
结果:
$ python test.py -m -P asfafs
usage: test.py [-h] [-v] [[-m | -p | --list <modules/ports>]
[-M module_name | -P fc_port_name | -I iSCSI_port_name | --simulate SIMULATE]
test.py: error: argument -P: not allowed with argument -m
$ python test.py -m -p
usage: test.py [-h] [-v] [[-m | -p | --list <modules/ports>]
[-M module_name | -P fc_port_name | -I iSCSI_port_name | --simulate SIMULATE]
test.py: error: argument -p: not allowed with argument -m