web-dev-qa-db-ja.com

argparseサブパーサーのモノリシックヘルプ出力

私のargparseには最上位に3つのフラグ(store_true)しかありません。それ以外はすべてサブパーサーで処理されます。 myprog.py --helpを実行すると、通常の{sub1, sub2, sub3, sub4, ...}のようなすべてのサブコマンドのリストが出力に表示されます。したがって、デフォルトはうまく機能しています...

通常、必要な正確なサブコマンド名とそのすべてのオプションを思い出せません。だから私は2つのヘルプルックアップをすることになります:

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

私はこれを頻繁に行うので、これを1つのステップにまとめることにしました。トップレベルのヘルプに大量の要約を出力させてから、リストを手動でスクロールします。私はそれがはるかに速いと感じました(少なくとも私にとっては)。

RawDescriptionHelpFormatterを使用していて、長いヘルプ出力を手動で入力していました。しかし、今では多くのサブコマンドがあり、管理するのが面倒になっています。

1回のプログラム呼び出しで詳細なヘルプ出力を取得する方法はありますか?

そうでない場合、どのようにして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)
28
user2097818

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 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())

この例は、python 2.7およびpython 3.で機能します。例のパーサーは、 Python 2.7 argparseに関するドキュメントですサブコマンド

あとは、完全なヘルプのために新しい引数を追加するか、組み込みの-h/--helpを置き換えるだけです。

15
Adaephon

カスタムヘルプハンドラーを使用した完全な解決策を次に示します(@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()
11
grundic

Adaephonの例でサブパーサーを反復するより簡単な方法は、

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

Pythonではparser._actionsなどの非表示の属性にアクセスできますが、これはお勧めできません。パーサーを定義しながら、独自のリストを作成するのも同じくらい簡単です。引数で特別なことをする場合も同様です。 add_argumentおよびadd_subparserは、理由により、それぞれのActionおよびParserオブジェクトを返します。

ArgumentParserのサブクラスを作成している場合は、_actionsを自由に使用できます。ただし、1回限りのアプリケーションの場合は、自分のリストを作成する方が明確です。


例:

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]

結合されたヘルプの表示は長く冗長です。フォーマット後、または特別なヘルプフォーマッタを使用して、さまざまな方法で編集できます。しかし、誰がそのような選択をするのでしょうか?

3
hpaulj

おそらく、より簡単な方法は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
0

_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))
0