Позиционные аргументы и подкоманды 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')
, однако, когда я изменяю аргумент 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
Почему это?
Ответы
Ответ 1
Сначала я думал так же, как jcollado, но тогда есть тот факт, что если последующие (верхние уровни) позиционные аргументы имеют конкретный nargs
(nargs
= None
, nargs
= integer), то он работает так, как вы ожидаете. Он терпит неудачу, если 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']
, чего вы не хотели.
Вкратце: беспорядок. Я предполагаю, что это следует считать ошибкой, но не уверен, каким будет воздействие, если шаблоны превратятся в неживые...
Ответ 2
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
), а не позиционным аргументом.
Ответ 3
Я думаю, что проблема в том, что при вызове 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
. Я бы сказал, что то, что вы ищете, это иметь позиционный аргумент перед именем subparser. Следовательно, вероятно, то, что вы ищете, добавляет аргумент перед подпарщиками:
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
Таким образом вы передаете сначала аргументы основному синтаксическому анализатору, затем имя подпарамера и, наконец, аргументы подпараллелу (если есть).
Ответ 4
Это все еще беспорядок в Python 3.5.
Я предлагаю subClass 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