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

时间:2013-02-02 10:19:17

标签: python arguments argparse

我开始学习Python,现在我正在学习argparse的巨大好处。 使用argparse,我创建了两组参数:group_listgroup_simulate。每个组都有自己的参数 - 用户只能在每个组中指定一个参数(使用parser.add_mutually_exclusive_group()实现)。

现在我的目标是一个语法错误,如果用户指定了来自两个groupgs的参数而不是仅来自其中一个 - 我想通过使用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>

我尝试使用来自add_subparsers的{​​{1}}选项来实现所需目标,但没有取得任何成功。

所以我的问题是如何实现这种情况?

3 个答案:

答案 0 :(得分:8)

您可以使用共同的互斥组作为两个子组的“根”:

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

答案 1 :(得分:1)

使用Docopt!您不必编写使用文档,然后花费数小时试图找出如何让argparse为您创建它。如果你知道POSIX,你知道如何解释用法文档,因为它是一个标准。 Docopt知道如何解释与您相同的用法文档。我们不需要抽象层。

我认为OP未能根据我在帮助文本中阅读的内容描述他们自己的意图。我将尝试推测他们想要做什么。

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非常有意义的原因。对于CLI程序,您需要确保清楚地与用户通信。为什么要学习一些模糊的模块语法,希望你可以说服它为你创建与用户的通信?花些时间查看其他POSIX工具,其中包含与您的需求相似的选项规则和copy-pasta。

答案 2 :(得分: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)OP设置为属性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 subparser机制。也就是说,有某种元解析器可以将动作委托给一个(或多个)子分析器。但是在argparse中,子分析符必须由用户明确命名。

可能的扩展是让元解析器查看上下文。例如,在OP情况下,如果它看到任何[--list,-p,-m]使用list子分析符,如果有任何simulate个参数,请使用simulate子分析器。这可能会提供一些更强大的分组工具。并且有可能用股票argparse实现这种事情。您可以在同一parsers上创建并运行多个不同的sys.argv