Как обрабатывать подкоманды 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,

Другие вопросы по тегам