argparse和optparse的子命令替代

时间:2015-03-06 09:21:48

标签: python argparse optparse

对于子命令,argparse / optparse是否有任何直观的替代方法?他们都很糟糕 - 这是疯狂的配置或疯狂的输出。

真实世界的例子(stolen,不想要):

>>> parser = argparse.ArgumentParser()
>>> subparsers = parser.add_subparsers(title='subcommands',
...                                    description='valid subcommands',
...                                    help='additional help')
>>> subparsers.add_parser('foo')
>>> subparsers.add_parser('bar')
>>> parser.parse_args(['-h'])
usage:  [-h] {foo,bar} ...

optional arguments:
  -h, --help  show this help message and exit

subcommands:
  valid subcommands

  {foo,bar}   additional help

通缉:

>>> parser = cmdline.Parser(
...   tplheader='Usage: tool [command] [options]',
...   tplcommandhead='Available commands:',
...   tplfooter='Use \"tool help\" to get full list of supported commands.')
>>> parser.add('foo', help='foo.')
>>> parser.add('bar', help='bar.')
>>> parser.parse(['-h'])
Usage: tool [command] [options]
Available commands:

  foo        foo.
  bar        bar.

Use "tool help" to get full list of supported commands.

UPDATE :我接受提供命令验证和解析示例的答案,该示例将帮助消息与最后一个片段完全一致。

5 个答案:

答案 0 :(得分:5)

只需稍微更改一下argparse代码,就可以非常接近所请求的输出:

  1. 通过将usage参数指定为ArgumentParser来设置使用文字。
  2. descriptionhelp个参数省略到add_subparsers
  3. title参数更改为Available subcommands
  4. 使用metavar参数覆盖难看的{foo,bar}文字。
  5. 使用help
  6. 中提供的add_parser参数

    这是成品:

    import argparse
    parser = argparse.ArgumentParser(usage='tool [command] [options]')
    subparsers = parser.add_subparsers(title='Available commands', metavar='')
    subparsers.add_parser('foo', help='foo.')
    subparsers.add_parser('bar', help='bar.')
    parser.parse_args(['-h'])
    

    该代码打印出来:

    usage: tool [command] [options]
    
    optional arguments:
      -h, --help  show this help message and exit
    
    Available commands:
    
        foo       foo.
        bar       bar.
    

答案 1 :(得分:0)

听起来像是在寻找argh

以下是主页上演示文稿的片段。

  

具有多个命令的潜在模块化应用程序:

import argh

# declaring:

def echo(text):
    "Returns given word as is."
    return text

def greet(name, greeting='Hello'):
    "Greets the user with given name. The greeting is customizable."
    return greeting + ', ' + name

# assembling:

parser = argh.ArghParser()
parser.add_commands([echo, greet])

# dispatching:

if __name__ == '__main__':
    parser.dispatch()
  

当然有效:

$ ./app.py greet Andy
Hello, Andy

$ ./app.py greet Andy -g Arrrgh
Arrrgh, Andy

网站上的帮助信息略有删节。以下是它实际输出的内容(argh 0.26.1)。

$ ./app.py --help
usage: app.py [-h] {greet,echo} ...

positional arguments:
  {greet,echo}
    echo        Returns given word as is.
    greet       Greets the user with given name. The greeting is customizable.

optional arguments:
  -h, --help    show this help message and exit

答案 2 :(得分:0)

我不确定我理解你描述的内容有什么问题。 我使用了稍微不同的东西:

parser = argparse.ArgumentParser(description='My description')
parser.add_argument('-i', '--input', type=str, required=True, help='Inputfile')
parser.add_argument('-o', '--output', type=str, required=False, help='Output file')
args = parser.parse_args()
input_filename = args.input
if not args.output:
    output_filename = input_filename
else:
    output_filename = args.output

答案 3 :(得分:0)

这会赢得奖品吗? :)

自定义参数

Rob Kennedy有更好的定制。

In [158]: parser=argparse.ArgumentParser(usage='tool [command] [options]',
  description= "Available commands:\n\n   foo    foo.\n   bar    bar.\n",
  epilog= 'Use "tool help" to get full list of supported commands',
  formatter_class=argparse.RawDescriptionHelpFormatter, add_help=False)

In [159]: parser.print_help()
usage: tool [command] [options]

Available commands:

   foo    foo.
   bar    bar.

Use "tool help" to get full list of supported commands

我所做的是使用可用参数自定义help

替代API和/或解析器?

但是您的其他行,parse.add()表示您不喜欢定义'命令的argparse方法。您可以向解析器添加一些使用这种更紧凑语法的方法,但最终仍然会调用现有的subparser机制。

但也许你想用自己的方法替换整个解析方案。例如,一个期望第一个参数是一个'命令'。其他'定位器&#39 ;?谁或什么处理“选项”?

您是否意识到argparse subparser方案是基于更基本的optionalspositionals解析方案构建的。 parser.add_subparsers命令是add_argument的一种特殊形式。 subparsers对象是一个位置参数,带有一个特殊的Action类。 {foo,bar}实际上是您为此参数定义的choices值的列表(子命令的名称或别名)。子命令本身就是解析器。

自定义前端命令解析器

如果sys.argv[1]项始终是command名称,您可以设置如下内容:

if sys.argv[1:]:
    cmd = sys.argv[1]
    rest = sys.argv[2:]
    parser = parser_dict.get(cmd, None)
    if parser:
        args = parser.parse_args(rest)
else:
    print_default_help()

其中parser_dict是将cmd字符串与定义的解析器匹配的字典。实际上,这只是捕获第一个参数字符串的前端,并将其余的处理调度到其他定义的解析器。它们可以是argparseoptparse和自定义解析器的混合体。如果它的所有处理都是第一个命令,那么这个前端并不一定是花哨的。字符串。

print_default_help只不过是parser_dict的精美印刷品。

进一步思考,我意识到sp.choices子分析器对象的argparse属性就是这样一个字典 - 命令字符串作为键,解析器作为值。

自定义format_help方法

以下是一些自定义帮助格式化程序。

一个只能从prog获取_choices_actionsparser的简单广告。 subparsers._choices_actions是包含各个子解析器的帮助和别名信息的对象列表。

def simple_help(parser, subparsers):
    # format a help message with just the subparser choices
    usage = "Usage: %s command [options]"%parser.prog
    desc = "Available commands:\n"
    epilog = '\nUse "%s help" to get full list of supported commands.'%parser.prog
    choices = fmt_choices(subparsers._choices_actions)
    astr = [usage]
    astr.append(desc)
    astr.extend(choices)
    astr.append(epilog)
    return '\n'.join(astr)

def fmt_choices(choices):
    # format names and help in 2 columns
    x = max(len(k.metavar) for k in choices)
    fmt = '   {:<%s}   {}'%x
    astr = []
    for k in choices:
        # k.metavar lists aliases as well
        astr.append(fmt.format(k.dest, k.help))
    return astr

这个模型基于parser.format_help建模,并使用Formatter及其所有包装和间距信息。我写它是为了尽可能使用非默认参数。但是,很难抑制空白行。

def special_help(parser, subparsers=None, usage=None, epilog=None):
    # format help message using a Formatter
    # modeled on parser.format_help
    # uses nondefault parameters where possible
    if usage is None:
        usage = "%(prog)s command [options]"
    if epilog is None:
        epilog = "Use '%(prog)s help' for command list"
    if subparsers is None:
        # find the subparsers action in the parser
        for action in parser._subparsers._group_actions:
            if hasattr(action, '_get_subactions'):
                subparsers = action
                break
        # if none found, subparsers is still None?
    if parser._subparsers != parser._positionals:
        title = parser._subparsers.title
        desc = parser._subparsers.description
    else:
        title = "Available commands"
        desc = None
    if subparsers.metavar is None:
        subparsers.metavar = '_________'
        # restore to None at end?
    formatter = parser._get_formatter()
    if parser.usage is None:
        formatter.add_usage(usage, [], [])
    else:
        formatter.add_usage(parser.usage,
            parser._actions, parser._mutually_exclusive_groups)
    # can I get rid of blank line here?
    formatter.start_section(title)
    formatter.add_text(desc)
    formatter.add_arguments([subparsers])
    formatter.end_section()
    formatter.add_text(epilog)
    return formatter.format_help()

可以用不同的方式调用它们。可以替换parser's format_help方法,因此可以使用-h选项以及parser.print_help()生成。

或者您可以包含help子命令。这符合epilog消息。 -h仍会产生完整,丑陋的帮助。

sp3 = sp.add_parser('help')  # help='optional help message'

并测试args

if args.cmd in ['help']:
    print(simple_help(parser, sp))
    # print(special_help(parser))

另一种选择是在sys.argv之前检查parser.parser_args,如果该列表不够长,或者包含help字符串,请调用帮助功能。这大约是Ipython绕过常规argparse帮助所做的事情。

答案 4 :(得分:0)

您应该看看Click。在文档中,单击...

  • 可自由组合,不受限制
  • 完全遵循Unix命令行约定
  • 支持直接从环境变量中加载值
  • 支持提示自定义值
  • 完全可嵌套且可组合
  • 在Python 2和3中的工作原理相同
  • 支持开箱即用的文件处理
  • 带有有用的常用帮助程序(获取终端尺寸,ANSI颜色,获取直接键盘输入,屏幕清除,查找配置路径,启动应用程序和编辑器等)

使用装饰器创建参数和选项非常直观。您可以通过如下所示创建组来创建子命令。

import click                                                    


@click.command()                                                
@click.option('--count', default=1, help='number of greetings') 
@click.argument('name')                                         
def hello(count, name):                                         
    for i in range(count):                                      
        print(f"{i}. Hello {name}")                             

@click.group()                                                  
def cli():                                                      
    pass                                                        

@cli.command()                                                  
def initdb():                                                   
    click.echo('Initialized the database')                      

@cli.command()                                                  
def dropdb():                                                   
    click.echo('Dropped the database')                          

if __name__ == "__main__":                                      
    cli() 

此代码的输出为:

$ python click-example.py --help
Usage: click-example.py [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  dropdb
  initdb