Как обрабатывать подкоманды CLI с помощью argparse
Мне нужно реализовать интерфейс командной строки, в котором программа принимает подкоманды.
Например, если программа называется "foo", CLI будет выглядеть так
foo cmd1 <cmd1-options>
foo cmd2
foo cmd3 <cmd3-options>
cmd1
а также cmd3
должен использоваться по крайней мере с одним из их вариантов и три cmd*
аргументы всегда исключительны.
Я пытаюсь использовать подпарсеры в argparse, но пока безуспешно. Проблема с cmd2
, что не имеет аргументов:
если я пытаюсь добавить запись подпаратера без аргументов, пространство имен возвращается parse_args
не будет содержать никакой информации о том, что эта опция была выбрана (см. пример ниже). если я попытаюсь добавить cmd2
в качестве аргумента parser
(не подпарсер), тогда argparse будет ожидать, что cmd2
Аргумент будет сопровождаться любым из аргументов подпарсеров.
Есть ли простой способ добиться этого с argparse
? Вариант использования должен быть довольно распространенным...
Ниже следует то, что я пытался сделать, это ближе к тому, что мне нужно:
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(help='Functions')
parser_1 = subparsers.add_parser('cmd1', help='...')
parser_1.add_argument('cmd1_option1', type=str, help='...')
parser_2 = subparsers.add_parser(cmd2, help='...')
parser_3 = subparsers.add_parser('cmd3', help='...')
parser_3.add_argument('cmd3_options', type=int, help='...')
args = parser.parse_args()
2 ответа
Прежде всего подпаперы никогда не вставляются в пространство имен. В приведенном вами примере, если вы попытаетесь запустить скрипт как:
$python3 test_args.py cmd1 1
Namespace(cmd1_option1='1')
где test_args.py
содержать код, который вы предоставили (с import argparse
в начале и print(args)
в конце).
Обратите внимание, что нет упоминания cmd1
только на свой аргумент. Это по замыслу.
Как указано в комментариях, вы можете добавить эту информацию, передавая dest
аргумент add_subparsers
вызов.
Обычный способ справиться с этими обстоятельствами заключается в использовании set_defaults
метод подпарасеров:
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(help='Functions')
parser_1 = subparsers.add_parser('cmd1', help='...')
parser_1.add_argument('cmd1_option1', type=str, help='...')
parser_1.set_defaults(parser1=True)
parser_2 = subparsers.add_parser('cmd2', help='...')
parser_2.set_defaults(parser2=True)
parser_3 = subparsers.add_parser('cmd3', help='...')
parser_3.add_argument('cmd3_options', type=int, help='...')
parser_3.set_defaults(parser_3=True)
args = parser.parse_args()
print(args)
Что приводит к:
$python3 test_args.py cmd1 1
Namespace(cmd1_option1='1', parser1=True)
$python3 test_args.py cmd2
Namespace(parser2=True)
В общем случае разные подпункты будут в большинстве случаев обрабатывать аргументы совершенно по-разному. Обычный шаблон - иметь разные функции для запуска разных команд и использования set_defaults
установить func
приписывать. Когда вы анализируете аргументы, вы просто называете это вызываемым:
subparsers = parser.add_subparsers()
parser_1 = subparsers.add_parser(...)
parser_1.set_default(func=do_command_one)
parser_k = subparsers.add_parser(...)
parser_k.set_default(func=do_command_k)
args = parser.parse_args()
if args.func:
args.func(args)
Идентификатор подпарсера можно добавить в основной Namespace
если add_subparsers
команда дается dest
,
Из документации:
Однако, если необходимо проверить имя вызванного подпаратера, аргумент ключевого слова dest к вызову add_subparsers() будет работать:
>>> parser = argparse.ArgumentParser()
>>> subparsers = parser.add_subparsers(dest='subparser_name')
>>> subparser1 = subparsers.add_parser('1')
>>> subparser1.add_argument('-x')
>>> subparser2 = subparsers.add_parser('2')
>>> subparser2.add_argument('y')
>>> parser.parse_args(['2', 'frobble'])
Namespace(subparser_name='2', y='frobble')
По умолчанию dest
является argparse.SUPPRESS
, который держит subparsers
от добавления имени к namespace
,