同时在python中解析多个子命令或以其他方式对解析的参数进行分组

时间:2015-07-22 10:01:15

标签: python argparse subcommand

我正在将Bash shell安装程序实用程序转换为Python 2.7,并且需要实现复杂的CLI,因此我能够解析数十个参数(可能最多约150个)。这些是Puppet类变量的名称以及十几个通用部署选项,这些选项在shell版本中可用。

然而,在我开始添加更多变量之后,我遇到了几个挑战: 1.我需要将参数分组到单独的词典中,以便将部署选项与Puppet变量分开。如果它们被抛入同一个桶中,那么我将不得不编写一些逻辑来对它们进行排序,可能重命名参数,然后字典合并将不会是微不足道的。 2.可能存在具有相同名称但属于不同Puppet类的变量,因此我认为子命令允许我过滤掉哪些内容并避免名称冲突。

我已经通过简单地添加多个解析器来实现参数解析:

parser = argparse.ArgumentParser(description='deployment parameters.')
env_select = parser.add_argument_group(None, 'Environment selection')
env_select.add_argument('-c', '--client_id',  help='Client name to use.')
env_select.add_argument('-e', '--environment', help='Environment name to use.')
setup_type = parser.add_argument_group(None, 'What kind of setup should be done:')
setup_type.add_argument('-i', '--install', choices=ANSWERS, metavar='', action=StoreBool, help='Yy/Nn Do normal install and configuration')
# MORE setup options
...
args, unk = parser.parse_known_args()
config['deploy_cfg'].update(args.__dict__)

pup_class1_parser = argparse.ArgumentParser(description=None)
pup_class1 = pup_class1_parser.add_argument_group(None, 'Puppet variables')
pup_class1.add_argument('--ad_domain', help='AD/LDAP domain name.')
pup_class1.add_argument('--ad_host', help='AD/LDAP server name.')
# Rest of the parameters

args, unk = pup_class1_parser.parse_known_args()
config['pup_class1'] = dict({})
config['pup_class1'].update(args.__dict__)
# Same for class2, class3 and so on.

这种方法的问题在于它没有解决问题2.此外,第一个解析器使用“-h”选项,其余参数未显示在帮助中。

我曾尝试使用example selected as an answer,但我无法同时使用这两个命令。

## This function takes the 'extra' attribute from global namespace and re-parses it to create separate namespaces for all other chained commands.
def parse_extra (parser, namespace):
  namespaces = []
  extra = namespace.extra
  while extra:
    n = parser.parse_args(extra)
    extra = n.extra
    namespaces.append(n)

  return namespaces

pp = pprint.PrettyPrinter(indent=4)

argparser=argparse.ArgumentParser()
subparsers = argparser.add_subparsers(help='sub-command help', dest='subparser_name')

parser_a = subparsers.add_parser('command_a', help = "command_a help")
## Setup options for parser_a
parser_a.add_argument('--opt_a1', help='option a1')
parser_a.add_argument('--opt_a2', help='option a2')

parser_b = subparsers.add_parser('command_b', help = "command_b help")
## Setup options for parser_a
parser_b.add_argument('--opt_b1', help='option b1')
parser_b.add_argument('--opt_b2', help='option b2')


## Add nargs="*" for zero or more other commands
argparser.add_argument('extra', nargs = "*", help = 'Other commands')

namespace = argparser.parse_args()
pp.pprint(namespace)
extra_namespaces = parse_extra( argparser, namespace )
pp.pprint(extra_namespaces)

让我进入:

$ python argtest.py command_b --opt_b1 b1 --opt_b2 b2 command_a --opt_a1 a1
usage: argtest.py [-h] {command_a,command_b} ... [extra [extra ...]]
argtest.py: error: unrecognized arguments: command_a --opt_a1 a1

当我尝试使用两个子解析器定义父级时,结果相同。

问题

  1. 我可以以某种方式使用parser.add_argument_group进行参数解析,还是仅用于帮助打印的分组?它将解决问题1而不会错过帮助副作用。将它作为parse_known_args(namespace=argument_group)传递(如果我正确地回想起我的实验)获取所有变量(没关系),但也得到所有Python对象的结果dict(这对于hieradata YAML不好)
  2. 第二个示例中我缺少哪些允许使用多个子命令?或者argparse是不可能的?
  3. 对命令行变量进行分组的其他建议是什么?我看过Click,但是对于我的任务没有找到比标准argparse更有优势。
  4. 注意:我是系统管理员,而不是程序员,因此请对我进行非对象样式编码。 :)

    谢谢

    分辨 参数分组通过hpaulj建议的答案解决。

    import argparse
    import pprint
    parser = argparse.ArgumentParser()
    
    group_list = ['group1', 'group2']
    
    group1 = parser.add_argument_group('group1')
    group1.add_argument('--test11', help="test11")
    group1.add_argument('--test12', help="test12")
    
    group2 = parser.add_argument_group('group2')
    group2.add_argument('--test21', help="test21")
    group2.add_argument('--test22', help="test22")
    
    args = parser.parse_args()
    pp = pprint.PrettyPrinter(indent=4)
    
    d = dict({})
    
    for group in parser._action_groups:
        if group.title in group_list:
            d[group.title]={a.dest:getattr(args,a.dest,None) for a in group._group_actions}
    
    print "Parsed arguments"
    pp.pprint(d)
    

    这让我得到了第1号问题的理想结果。直到我将有多个具有相同名称的参数。解决方案可能看起来很难看,但至少它按预期工作。

    python argtest4.py --test22 aa  --test11 yy11 --test21 aaa21
    Parsed arguments
    {   'group1': {   'test11': 'yy11', 'test12': None},
        'group2': {   'test21': 'aaa21', 'test22': 'aa'}}
    

2 个答案:

答案 0 :(得分:0)

您的问题太复杂,无法理解并一次性回应。但我会抛出一些初步想法。

是的,argument_groups只是一种在帮助中对参数进行分组的方法。它们对解析没有影响。

另一个最近的SO询问解析参数组:

Is it possible to only parse one argument group's parameters with argparse?

该海报最初想要使用一个组作为解析器,但argparse类结构不允许这样做。 argparse以对象样式编写。 parser=ArguementParser...创建了一个对象类,parser.add_arguement...创建了另一个对象add_argument_group...。您可以通过继承ArgumentParserHelpFormatterAction类等来自定义它。

我提到了parents机制。您可以定义一个或多个父解析器,并使用这些解析器填充您的主要解析器'解析器。它们可以独立运行(使用parse_known_args),而主要是'用于处理帮助。

我们还讨论了在解析后对参数进行分组。 namespace是一个简单的对象,其中每个参数都是一个属性。它也可以转换为字典。从字典中提取项目组很容易。

关于使用多个subparser还有一些问题。这是一个尴尬的主张。可能,但不容易。 Subparser就像向系统程序发出命令一样。您通常每次调用发出一个命令。你不会嵌套它们或发出序列。您让shell管道和脚本处理多个操作。

IPython使用argparse来解析其输入。它首先捕获帮助,并发出自己的消息。大多数参数来自配置文件,因此可以使用默认配置,自定义配置和命令行设置值。这是一个命名一大堆参数的例子。

Subparsers允许您使用相同的参数名称,但无法在一次调用中调用多个子分析器,但这些子分析器并没有多大帮助。即使您可以调用多个子分析程序,它们仍然会将参数放在同一名称空间中。此外,argparse尝试以与顺序无关的方式处理flaged参数。因此,命令行末尾的--foo的解析方式与开始时的解析方式相同。

我们讨论了使用参数名称(' dest')和'group1.argument1'进行讨论的问题,我甚至讨论过使用嵌套命名空间。如果有帮助的话,我可以看一下。

另一个想法 - 加载sys.argv并在将其传递给一个或多个解析器之前对其进行分区。您可以将其拆分为某个关键字或前缀等。

答案 1 :(得分:0)

如果你有这么多论点,这似乎是一个设计问题。这似乎非常难以管理。您是否可以使用具有合理默认设置的配置文件来实现此功能?或者在代码中默认使用命令行中合理的(即SMALL)参数,并允许使用'key:value'配置文件中的参数覆盖所有内容或其他所有内容?我无法想象必须使用具有您提议的变量数量的CLI。