argparse необязательный позиционный аргумент и аргументы подпарсеров

У меня есть скрипт Python, который принимает необязательный позиционный аргумент и имеет несколько подкоманд. Некоторым из этих подкоманд требуется позиционный аргумент, некоторым — нет. Проблема у меня возникает, когда я пытаюсь использовать подкоманду, которая не требует позиционного аргумента. Рассмотрим следующий тестовый файл:

import argparse

argp = argparse.ArgumentParser()
argp.add_argument('inputfile', type=str, nargs='?',
                  help='input file to process')
argp.add_argument('--main_opt1', type=str,
                  help='global option')

subp = argp.add_subparsers(title='subcommands',
                           dest='parser_name',
                           help='additional help',
                           metavar="<command>")

tmpp = subp.add_parser('command1', help='command1 help')
tmpp.add_argument('pos_arg1', type=str,
                  help='positional argument')

print repr(argp.parse_args())

Когда я пытаюсь использовать подкоманду command1 с первым аргументом, все идет хорошо.

macbook-pro:~ jmlopez$ python pytest.py filename command1 otherarg
Namespace(inputfile='filename', main_opt1=None, parser_name='command1', pos_arg1='otherarg')

Но теперь предположим, что command1 не нуждается в первом позиционном аргументе.

macbook-pro:~ jmlopez$ python pytest.py command1 otherarg
usage: pytest.py [-h] [--main_opt1 MAIN_OPT1] [inputfile] <command> ...
pytest.py: error: argument <command>: invalid choice: 'otherarg' (choose from 'command1')

Я почему-то ожидал, что inputfile будет установлено на None. Есть ли способ, которым argparse может предсказать, что command1 на самом деле является подкомандой и, следовательно, inputfile должно быть установлено в None?


person jmlopez    schedule 23.12.2013    source источник


Ответы (2)


Для argp аргумент подпарсера выглядит точно так же, как другой позиционный, который принимает выбор (имена подпарсеров). Также argp ничего не знает о pos_arg1. Это в списке аргументов tmpp.

Когда argp видит filename command1 otherarg, filename и command1 удовлетворяют его 2 позиции. Затем otherarg передается на tmpp.

С command1 otherarg опять 2 струны, 2 argp позиционные. command назначается inputfile. Нет никакой логики, чтобы вернуться назад и сказать, что command1 лучше соответствует subcommands, или что `tmpp' нужна одна из этих строк.

Вы можете изменить 1-ю позицию на необязательную, --inputfile.

Или вы могли бы inputfile другую позицию tmpp. Если в этом нуждаются несколько подпарсеров, рассмотрите возможность использования parents.

argparse не такой умный, как ты, и не может «думать наперед» или «отступать». Если кажется, что он делает что-то умное, это потому, что он использует сопоставление шаблонов re для обработки nargs значений (например, ?, *, +).

РЕДАКТИРОВАТЬ

Один из способов «обмануть» argparse, чтобы он распознал первый позиционный элемент как подпарсер, — это вставить необязательный элемент после него. С command1 -b xxx otherarg -b xxx разбивает список позиционных строк, поэтому только command1 сопоставляется с inputfile и subcommands.

p=argparse.ArgumentParser()
p.add_argument('file',nargs='?',default='foo')
sp = p.add_subparsers(dest='cmd')
spp = sp.add_parser('cmd1')
spp.add_argument('subfile')
spp.add_argument('-b')

p.parse_args('cmd1 -b x three'.split())
# Namespace(b='x', cmd='cmd1', file='foo', subfile='three')

Проблема здесь в том, как argparse обрабатывает посты с переменной nargs. Тот факт, что 2-й позиционный является субпарсером, не важен. Хотя argparse допускает позиционные переменные длины в любом порядке, то, как он их обрабатывает, может сбить с толку. Легче предсказать, что сделает argparse, если есть только одна такая позиция, и она встречается в конце.

person hpaulj    schedule 24.12.2013
comment
Кажется, что так. Я предполагаю, что единственный способ добиться того, что я хочу, - это подготовить sys.argv и вставить входной файл по умолчанию или команду по умолчанию, если это необходимо, а затем позволить argparse выполнять свою работу. - person jmlopez; 24.12.2013
comment
Важно ли, чтобы inputfile происходило перед subcommands? Обычно (как в программах типа git) подпарсер является первым позиционным аргументом. Если inputfile обрабатывается command1 особым образом, я ожидаю, что имя будет указано в конце командной строки. - person hpaulj; 24.12.2013
comment
Я добавил частичное решение — добавление аргумента optional (с флажком) в подразбор, чтобы разбить список строк positional. - person hpaulj; 25.12.2013

вам нужно сообщить синтаксическому анализатору, что первый аргумент имеет другой тип. попробуйте добавить опцию флагов и значение по умолчанию None следующим образом:

argp.add_argument('-i','--inputfile', type=str, nargs='?',
              help='input file to process',default=None)

теперь вам нужно добавить -i перед аргументом inputfile, но это будет работать нормально.

macbook-pro:~ jmlopez$ python pytest.py -i filename command1 otherarg
Namespace(inputfile='filename', main_opt1=None, parser_name='command1', pos_arg1='otherarg')

а также

macbook-pro:~ jmlopez$ python pytest.py command1 otherarg
Namespace(inputfile=None, main_opt1=None, parser_name='command1', pos_arg1='otherarg')
person Elisha    schedule 23.12.2013
comment
Имея это, как и раньше, я мог сделать macbook-pro:~ jmlopez$ python pytest.py _ command1 otherarg, чтобы сообщить python, что входной аргумент пуст. У меня есть вариант, скажем, --cmd, чтобы указать, что первое ключевое слово является командой, а не вводом. - person jmlopez; 23.12.2013