【问题标题】:argparse subparser monolithic help outputargparse 子解析器整体帮助输出
【发布时间】:2013-12-04 08:15:04
【问题描述】:

我的 argparse 在顶层只有 3 个标志 (store_true),其他一切都通过子解析器处理。当我运行myprog.py --help 时,输出会显示所有子命令的列表,如正常{sub1, sub2, sub3, sub4, ...}。所以,默认设置很好......

我通常不记得我需要的确切子命令名称及其所有选项。所以我最终做了 2 次帮助查找:

myprog.py --help
myprog.py sub1 --help

我经常这样做,所以我决定把它塞进一步。我宁愿让我的顶级帮助输出一个巨大的摘要,然后我手动滚动列表。我发现它要快得多(至少对我来说)。

我使用的是 RawDescriptionHelpFormatter,并手动输入长帮助输出。但是现在我有很多子命令,管理起来很麻烦。

有没有一种方法可以通过一个程序调用来获得详细的帮助输出?

如果没有,我如何迭代我的 argparse 实例的子解析器,然后从每个子解析器中单独检索帮助输出(稍后我会将它们粘合在一起)?


这是我的 argparse 设置的简要概述。我清理/剥离了一些代码,所以如果没有一点帮助,这可能无法运行。

parser = argparse.ArgumentParser(
        prog='myprog.py',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        description=textwrap.dedent(""" You can manually type Help here """) )

parser.add_argument('--debuglog', action='store_true', help='Verbose logging for debug purposes.')
parser.add_argument('--ipyonexit', action='store_true', help='Drop into an embeded Ipython session instead of exiting command.')

subparser = parser.add_subparsers()

### --- Subparser B
parser_b = subparser.add_parser('pdfreport', description="Used to output reports in PDF format.")
parser_b.add_argument('type', type=str, choices=['flatlist', 'nested', 'custom'],
                        help="The type of PDF report to generate.")
parser_b.add_argument('--of', type=str, default='',
                        help="Override the path/name of the output file.")
parser_b.add_argument('--pagesize', type=str, choices=['letter', '3x5', '5x7'], default='letter',
                        help="Override page size in output PDF.")
parser_b.set_defaults(func=cmd_pdf_report)

### ---- Subparser C
parser_c = subparser.add_parser('dbtables', description="Used to perform direct DB import/export using XLS files.")
parser_c.add_argument('action', type=str, choices=['push', 'pull', 'append', 'update'],
                        help="The action to perform on the Database Tables.")
parser_c.add_argument('tablename', nargs="+",
                        help="The name(s) of the DB-Table to operate on.")
parser_c.set_defaults(func=cmd_db_tables)

args = parser.parse_args()
args.func(args)

【问题讨论】:

  • 向我们展示一个带有一些代码的小例子,只有几个选项和几个子解析器。

标签: python argparse


【解决方案1】:

这有点棘手,因为 argparse 不直接公开已定义子解析器的列表。但可以做到:

import argparse

# create the top-level parser
parser = argparse.ArgumentParser(prog='PROG')
parser.add_argument('--foo', action='store_true', help='foo help')
subparsers = parser.add_subparsers(help='sub-command help')

# create the parser for the "a" command
parser_a = subparsers.add_parser('a', help='a help')
parser_a.add_argument('bar', type=int, help='bar help')

# create the parser for the "b" command
parser_b = subparsers.add_parser('b', help='b help')
parser_b.add_argument('--baz', choices='XYZ', help='baz help')
# print main help
print(parser.format_help())

# retrieve subparsers from parser
subparsers_actions = [
    action for action in parser._actions 
    if isinstance(action, argparse._SubParsersAction)]
# there will probably only be one subparser_action,
# but better safe than sorry
for subparsers_action in subparsers_actions:
    # get all subparsers and print help
    for choice, subparser in subparsers_action.choices.items():
        print("Subparser '{}'".format(choice))
        print(subparser.format_help())

这个例子应该适用于 python 2.7 和 python 3。例子解析器来自Python 2.7 documentation on argparse sub-commands

剩下要做的就是为完整的帮助添加一个新参数,或者替换内置的-h/--help

【讨论】:

  • 很好的例子。这对我来说产生了很好的输出。我不确定如何在我的情况下重新定义 -h/--help 参数,因为可选参数不喜欢跟随我的子解析器。不过,我可能只是将另一个名为“help”的子解析器定义为最后一个,它可以检查在它之前添加的所有内容。
  • 我添加了另一个名为 help 的子解析器。这个解决方案很棒,因为我可以把它变成一个只接受“解析器”的函数。
  • 为什么不使用hasattr() 而不是列表解析和for 循环?
【解决方案2】:

这是带有自定义帮助处理程序的完整解决方案(几乎所有代码都来自@Adaephon 答案):

import argparse


class _HelpAction(argparse._HelpAction):

    def __call__(self, parser, namespace, values, option_string=None):
        parser.print_help()

        # retrieve subparsers from parser
        subparsers_actions = [
            action for action in parser._actions
            if isinstance(action, argparse._SubParsersAction)]
        # there will probably only be one subparser_action,
        # but better save than sorry
        for subparsers_action in subparsers_actions:
            # get all subparsers and print help
            for choice, subparser in subparsers_action.choices.items():
                print("Subparser '{}'".format(choice))
                print(subparser.format_help())

        parser.exit()

# create the top-level parser
parser = argparse.ArgumentParser(prog='PROG', add_help=False)  # here we turn off default help action

parser.add_argument('--help', action=_HelpAction, help='help for help if you need some help')  # add custom help

parser.add_argument('--foo', action='store_true', help='foo help')
subparsers = parser.add_subparsers(help='sub-command help')

# create the parser for the "a" command
parser_a = subparsers.add_parser('a', help='a help')
parser_a.add_argument('bar', type=int, help='bar help')

# create the parser for the "b" command
parser_b = subparsers.add_parser('b', help='b help')
parser_b.add_argument('--baz', choices='XYZ', help='baz help')

parsed_args = parser.parse_args()

【讨论】:

  • 最好使用parser.add_argument ('-h', '--help', action=_HelpAction, help='show this help message and exit') 来匹配默认的argparse --help 选项。
  • 不提供参数时如何打印帮助?
  • @kanna,请看这个答案:stackoverflow.com/a/4042861/279355
【解决方案3】:

也许更简单的方法是使用parser.epilog

def define_parser():
    import argparse
    parser = argparse.ArgumentParser(
        prog='main',
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    commands = parser.add_subparsers(
        title="required commands",
        help='Select one of:',
    )    
    command_list = commands.add_parser(
        'list',
        help='List included services',
    )
    command_ensure = commands.add_parser(
        'ensure',
        help='Provision included service',
    )
    command_ensure.add_argument(
        "service",
        help='Service name',
    )
    import textwrap
    parser.epilog = textwrap.dedent(
        f"""\
        commands usage:\n
        {command_list.format_usage()}
        {command_ensure.format_usage()}
        """
    )
    return parser

parser = define_parser()

parser.print_help()

导致以下输出:

usage: main [-h] {list,ensure} ...

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

required commands:
  {list,ensure}  Select one of:
    list         List included services
    ensure       Provision included service

commands usage:

usage: main list [-h]

usage: main ensure [-h] service

【讨论】:

  • 不是最好的布局,但喜欢简单。 textwrap 并不是必须的,我们可以通过将所有用法都放在一行来删除多余的换行符。
  • 实际上,我们也可以直接更改usage,注意去掉输出中的前缀Usage:parser.usage=f"{parser.format_usage()[7:]}{command_list.format_usage()}{command_ensure.format_usage()}"。这提供了几乎完美的布局。
【解决方案4】:

在 Adaephon 的示例中迭代子解析器的更简单方法是

for subparser in [parser_a, parser_b]:
   subparser.format_help()

Python 确实允许您访问隐藏属性,例如 parser._actions,但不鼓励这样做。在定义解析器时构建自己的列表同样容易。对参数做特殊的事情也是如此。 add_argumentadd_subparser 返回它们各自的 ActionParser 对象是有原因的。

如果我要创建ArgumentParser 的子类,我可以随意使用_actions。但是对于一次性应用程序,建立我自己的列表会更清晰。


一个例子:

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('mainpos')
parser.add_argument('--mainopt')
sp = parser.add_subparsers()
splist = []   # list to collect subparsers
sp1 = sp.add_parser('cmd1')
splist.append(sp1)
sp1.add_argument('--sp1opt')
sp2 = sp.add_parser('cmd2')
splist.append(sp2)
sp2.add_argument('--sp2opt')

# collect and display for helps    
helps = []
helps.append(parser.format_help())
for p in splist:
   helps.append(p.format_help())
print('\n'.join(helps))

# or to show just the usage
helps = []
helps.append(parser.format_usage())
for p in splist:
   helps.append(p.format_usage())
print(''.join(helps))

组合的“使用”显示为:

usage: stack32607706.py [-h] [--mainopt MAINOPT] mainpos {cmd1,cmd2} ...
usage: stack32607706.py mainpos cmd1 [-h] [--sp1opt SP1OPT]
usage: stack32607706.py mainpos cmd2 [-h] [--sp2opt SP2OPT]

组合帮助的显示冗长且多余。可以在格式化后或使用特殊帮助格式化程序以各种方式对其进行编辑。但是谁会做出这样的选择呢?

【讨论】:

    【解决方案5】:

    我还能够使用_choices_actions 打印命令的简短帮助。

    def print_help(parser):
      print(parser.description)
      print('\ncommands:\n')
    
      # retrieve subparsers from parser
      subparsers_actions = [
          action for action in parser._actions 
          if isinstance(action, argparse._SubParsersAction)]
      # there will probably only be one subparser_action,
      # but better save than sorry
      for subparsers_action in subparsers_actions:
          # get all subparsers and print help
          for choice in subparsers_action._choices_actions:
              print('    {:<19} {}'.format(choice.dest, choice.help))
    

    【讨论】:

      【解决方案6】:

      add_subparsers().add_parser() 不仅接受description,它显示在子命令的帮助中,还接受一个help=,它在顶级解析器的帮助中用作单行描述。

      docs 将其隐藏在公式中

      (但是,可以通过向 add_parser() 提供 help= 参数来提供每个子解析器命令的帮助消息。)

      甚至在该句子周围的示例代码中:

      >>> # create the parser for the "b" command
      >>> parser_b = subparsers.add_parser('b', help='b help')
      >>> parser_b.add_argument('--baz', choices='XYZ', help='baz help')
      

      [...]

      usage: PROG [-h] [--foo] {a,b} ...
      
      positional arguments:
        {a,b}   sub-command help
          a     a help
          b     b help
      

      是的,这并不是对所有事情的全部帮助,但恕我直言,它很好地涵盖了基本用例,而且不容易被发现。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-03-23
        • 1970-01-01
        • 2012-06-19
        • 2013-02-01
        • 2013-03-08
        • 2015-12-11
        • 1970-01-01
        • 2018-12-31
        相关资源
        最近更新 更多