使用argparse处理组之间的Python依赖关系

9 投票
3 回答
11334 浏览
提问于 2025-04-17 14:31

我开始学习Python,现在我正在了解使用argparse的好处。通过argparse,我创建了两个参数组:group_listgroup_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 个回答

0

这个解析器的简单版本是:

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 替代了它),原作者还设置了属性 listsimulatelist 的默认值是 all,也可以设置为 modulesports-m-p 只是快捷方式,并没有真正增加定义的内容。当定义很多选项时,快捷方式会很方便,尤其是这些选项可以一起使用(例如 -vpm)。不过在这里,除了 -v 之外,只允许一个选项。

simulate 接受一个没有限制的字符串。M/P/C 选项只是为了方便文档,并没有限制值或增加意义。

这是一个很好的练习,可以推动 argparse(或其他解析器)的边界,但我觉得它太复杂了,实用性不高。尽管有很多分组,但最终只允许一个选项。

==========================

关于 docoptPOSIX 参数处理的评论让我去看看 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

1

使用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工具的选项规则,然后直接复制粘贴就好了。

9

你可以把一个常见的互斥组当作两个子组的“根”:

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

撰写回答