Python argparse позиционные аргументы и подкоманды
Я работаю с argparse и пытаюсь смешать подкоманды и позиционные аргументы, и возникла следующая проблема.
Этот код работает нормально:
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
parser.add_argument('positional')
subparsers.add_parser('subpositional')
parser.parse_args('subpositional positional'.split())
Приведенный выше код анализирует аргументы в Namespace(positional='positional')
Однако, когда я изменяю позиционный аргумент, чтобы иметь Nargs='?' в качестве таких:
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
parser.add_argument('positional', nargs='?')
subparsers.add_parser('subpositional')
parser.parse_args('subpositional positional'.split())
Это ошибки с:
usage: [-h] {subpositional} ... [positional]
: error: unrecognized arguments: positional
Почему это?
4 ответа
Сначала я думал так же, как jcollado, но потом есть тот факт, что, если последующие позиционные аргументы (верхнего уровня) имеют определенный nargs
(nargs
знак равно None
, nargs
= целое число), тогда он работает, как вы ожидаете. Это не удается, когда nargs
является '?'
или же '*'
и иногда, когда это '+'
, Итак, я пошел к коду, чтобы выяснить, что происходит.
Это сводится к тому, как аргументы разделяются для использования. Чтобы выяснить, кто что получает, позвоните parse_args
суммирует аргументы в виде строки 'AA'
, в твоем случае ('A'
для позиционных аргументов, 'O'
для необязательного) и заканчивает тем, что создает шаблон регулярного выражения для сопоставления с этой строкой резюме, в зависимости от действий, которые вы добавили в анализатор через .add_argument
а также .add_subparsers
методы.
В каждом случае, например, строка аргумента заканчивается 'AA'
, Какие изменения - это шаблон, который нужно сопоставить (вы можете увидеть возможные шаблоны в _get_nargs_pattern
в argparse.py
, За subpositional
это в конечном итоге '(-*A[-AO]*)'
, что означает, что допускается один аргумент, за которым следует любое количество опций или аргументов. За positional
, это зависит от значения, переданного nargs
:
None
=>'(-*A-*)'
- 3 =>
'(-*A-*A-*A-*)'
(один'-*A'
за ожидаемый аргумент) '?'
=>'(-*A?-*)'
'*'
=>'(-*[A-]*)'
'+'
=>'(-*A[A-]*)'
Эти шаблоны добавляются и, для nargs=None
(ваш рабочий пример), вы в конечном итоге '(-*A[-AO]*)(-*A-*)'
, которая соответствует двум группам ['A', 'A']
, Сюда, subpositional
будет разбирать только subpositional
(что ты хотел), пока positional
будет соответствовать его действию.
За nargs='?'
тем не менее, вы в конечном итоге '(-*A[-AO]*)(-*A?-*)'
, Вторая группа полностью состоит из необязательных шаблонов и *
будучи жадным, это означает, что первая группа теряет все в строке, заканчивая тем, что распознала две группы ['AA', '']
, Это означает subpositional
получает два аргумента и в конце концов задыхается, конечно.
Достаточно забавно, шаблон для nargs='+'
является '(-*A[-AO]*)(-*A[A-]*)'
, который работает до тех пор, пока вы передаете только один аргумент. Сказать subpositional a
, так как вам требуется хотя бы один позиционный аргумент во второй группе. Опять же, поскольку первая группа жадная, прохождение subpositional a b c d
получает вас ['AAAA', 'A']
что не то, что вы хотели.
Вкратце: беспорядок. Я думаю, это следует считать ошибкой, но я не уверен, как это повлияет, если шаблоны превратятся в не жадных...
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('positional', nargs='?')
subparsers = parser.add_subparsers()
subparsers.add_parser('subpositional')
print(parser.parse_args(['positional', 'subpositional']))
# -> Namespace(positional='positional')
print(parser.parse_args(['subpositional']))
# -> Namespace(positional=None)
parser.print_usage()
# -> usage: bpython [-h] [positional] {subpositional} ...
Обычная практика заключается в том, что аргументы перед командой (слева) принадлежат основной программе, а после (справа) - команде. Следовательно positional
должен идти перед командой subpositional
, Примеры программ: git
, twistd
,
Дополнительно аргумент с narg=?
должно быть вариант (--opt=value
), а не позиционный аргумент.
Я думаю, что проблема в том, что когда add_subparsers
вызывается, новый параметр добавляется в исходный анализатор для передачи имени подпарамера.
Например, с этим кодом:
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
parser.add_argument('positional')
subparsers.add_parser('subpositional')
parser.parse_args()
Вы получите следующую строку справки:
usage: test.py [-h] {subpositional} ... positional
positional arguments:
{subpositional}
positional
optional arguments:
-h, --help show this help message and exit
Обратите внимание, что subpositional
отображается перед positional
, Я бы сказал, что вам нужно иметь позиционный аргумент перед именем подпарапера. Следовательно, вероятно, что вы ищете, это добавить аргумент перед подпарсерами:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('positional')
subparsers = parser.add_subparsers()
subparsers.add_parser('subpositional')
parser.parse_args()
Строка справки, полученная с помощью этого кода:
usage: test.py [-h] positional {subpositional} ...
positional arguments:
positional
{subpositional}
optional arguments:
-h, --help show this help message and exit
Таким образом, вы сначала передаете аргументы основному парсеру, затем имя подпаратера и, наконец, аргументы подпарсеру (если есть).
Это все еще беспорядок в Python 3.5.
Я предлагаю подклассу ArgumentParser сохранить все оставшиеся позиционные аргументы и разобраться с ними позже:
import argparse
class myArgumentParser(argparse.ArgumentParser):
def parse_args(self, args=None, namespace=None):
args, argv = self.parse_known_args(args, namespace)
args.remaining_positionnals = argv
return args
parser = myArgumentParser()
options = parser.parse_args()
Остальные позиционные аргументы находятся в списке options.remaining_positionals