Позиционные аргументы и подкоманды 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