【问题标题】:Subcommand alternative to argparse and optparseargparse 和 optparse 的替代子命令
【发布时间】:2015-05-07 20:59:00
【问题描述】:

对于子命令的 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.

更新:我会接受提供命令验证和解析示例的答案,该示例提供的帮助消息与最后一个 sn-p 完全相同。

【问题讨论】:

  • 'output' - 是指帮助信息,还是带有解析值的对象?
  • 您可能想看看Click。有关子命令的详细信息,请参阅commands and groups 上的文档。
  • @hpaulj, output 是帮助信息。修正了描述。

标签: python argparse optparse


【解决方案1】:

听起来您正在寻找argh

这是主页上演示文稿中的一个 sn-p。

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

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

网站上的帮助信息略有删节。这是它实际为我输出的内容 (argh0.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

【讨论】:

  • ./app.py -h 呢?
  • 使用网站副本更新了答案,但实际上,请检查链接以获取其余部分。
  • 它在锡上是完全没有帮助的。不过,它所做的要好得多。
  • pypi.python.org/pypi/plac 是另一个建立在argparse 之上的解析器。您通过指定将被调用的函数来定义“命令”。它使用函数参数来填充解析器参数。
  • @tripleee,是否允许更改positional arguments: 行? svn help 也有大约 10 多个命令。它们会全部堆放在使用线上吗?
【解决方案2】:

我不确定我是否理解您所描述的内容有什么问题。 不过我使用了一些稍微不同的东西:

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

【讨论】:

  • 您根本不使用子命令。尝试执行hgsvn help 输出以查看差异。
  • 哦,现在我明白你的意思了。好主意,我也将开始使用这些......一旦你的问题得到了很好的答案:-)
【解决方案3】:

这能中奖吗? :)

自定义参数

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 机制。

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

您是否意识到argparse 子解析器方案是建立在更基本的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 的漂亮打印。

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

自定义 format_help 方法

这里有几个自定义帮助格式化程序。

仅从parser 获取prog_choices_actions 的简单方法。 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))

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

【讨论】:

  • 我可能可以通过复制粘贴多页类定义来破解 argparser,但我更愿意使用一个简短且直观且不会让我的命令行助手负担过重的库。
  • 通常,argparse 帮助格式化程序通过更改格式化程序类的几个方法来进行调整。显然,您确实必须对代码进行足够的研究才能知道在哪里进行这些少量更改。没有人会在盘子上给你一个自定义格式化程序。
【解决方案4】:

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

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

这是成品:

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'])

那段代码打印了这个:

用法:工具 [命令] [选项] 可选参数: -h, --help 显示此帮助信息并退出 可用命令: 富富。 酒吧酒吧。

【讨论】:

  • 添加一个“epilog”和“add_help=False”,你会更接近。我的答案想太多了。
  • 我故意省略了结尾文本,因为需要额外的工作才能使请求的文本成为对行为的准确描述,@Hpaulj。现有命令似乎对是通过专用子命令还是通过选项访问帮助存在分歧,并且选项方法已经内置到 argparse 库中。
  • @RobKennedy,不错的尝试。这可能是一个很好的答案来说明为什么argparse 不能完成这项工作。它看起来几乎没问题,但我怀疑要完全按照要求获得所需的输出并不容易。
  • @hpaulj,它显示optional arguments 垃圾并且不显示页脚。
【解决方案5】:

你应该看看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

【讨论】:

    猜你喜欢
    • 2018-12-30
    • 1970-01-01
    • 2020-01-14
    • 2015-06-02
    • 2012-09-11
    • 2012-01-29
    • 2017-09-02
    • 2018-12-03
    • 2013-08-19
    相关资源
    最近更新 更多