Выводной монолитной справки subparser argparse
Мой argparse имеет только 3 флага (store_true) на верхнем уровне, все остальное обрабатывается через подпарамеры. Когда я запускаю myprog.py --help
, на выходе отображается список всех подкоманд, таких как normal, {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)
Ответы
Ответ 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 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
.
Ответ 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()
Ответ 3
Более простой способ итерации над подпараметрами в примере Adaephon -
for subparser in [parser_a, parser_b]:
subparser.format_help()
Python позволяет вам получить доступ к скрытым атрибутам типа parser._actions
, но это не рекомендуется. Так же легко создавать свой собственный список при определении парсера. То же самое относится к тому, чтобы делать специальные вещи с аргументами. add_argument
и add_subparser
по какой-либо причине возвращают соответствующие объекты Action
и Parser
.
Если бы я делал подкласс 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]
Отображение комбинированных данных является длинным и избыточным. Его можно редактировать по-разному, либо после форматирования, либо с помощью специальных форм помощи. Но кто собирается делать такой выбор?
Ответ 4
Я также смог распечатать краткую справку для команд с помощью _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))
Ответ 5
Возможно, более простой подход - использовать 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
Ответ 6
У меня есть несколько простых оберток, которые хранят различные ссылки (Parser, SubParser, StoreAction) последовательно, для легкой итерации во время создания справки.
Теперь я получаю организованный, подробный, автоматически генерируемый вывод справки. Когда я получу шанс, я опубликую обзор.
Есть один недостаток, и это связано с созданным содержимым справки по необязательным аргументам: это не очень хорошо. Для улучшения этих результатов помощи потребуется больше, чем обертка (если мы хотим сохранить ее в чистоте). Но если вам нужен хороший обзор справки для разрабатываемых программ, это должно удовлетворить большинство.