Python argparse позиционные аргументы и подкоманды - PullRequest
11 голосов
/ 29 декабря 2011

Я работаю с 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 ]

9 голосов
/ 29 декабря 2011

Сначала я думал так же, как 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 получает два аргумента, aИ, конечно же, он заканчивается удушением.

Достаточно забавно, шаблон для nargs='+' равен '(-*A[-AO]*)(-*A[A-]*)', который работает , если вы передаете только один аргумент .Скажите subpositional a, так как вам требуется хотя бы один позиционный аргумент во второй группе.Опять же, поскольку первая группа жадная, пропуск subpositional a b c d дает вам ['AAAA', 'A'], а это не то, что вы хотели.

Вкратце: беспорядок.Я думаю, это следует считать ошибкой, но я не уверен, какое влияние это окажет, если шаблоны превратятся в не жадных ...

6 голосов
/ 29 декабря 2011
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), а не позиционным аргументом.

5 голосов
/ 29 декабря 2011

Я думаю, что проблема в том, что при вызове 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

Таким образом, вы передаете сначалааргументы для основного синтаксического анализатора, затем имя подпаратера и, наконец, аргументы для подпарсера (если есть).

0 голосов
/ 09 января 2016

Это все еще беспорядок в 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

...