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())
приведенный выше код анализирует args в 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 = integer), то он работает так, как вы ожидаете. Он не nargs is '?' или '*', а иногда, когда это '+'. Итак, я спустился к коду, чтобы выяснить, что происходит.
это сводится к тому, как аргументы разделяются для потребления. Чтобы выяснить, кто что получает, вызов к 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='+' is '(-*A[-AO]*)(-*A[A-]*)', который работает пока вы передаете только один аргумент. Скажи subpositional a, поскольку вам требуется хотя бы один позиционный аргумент во второй группе. Опять же, как первая группа жадная, проходящая subpositional a b c d получает ['AAAA', 'A'], что не то, что вы хотели.
вкратце: 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. Я бы сказал, что вы ищете, чтобы иметь позиционный аргумент перед именем subparser. Следовательно, вероятно, то, что вы ищете, - это добавление аргумента перед subparsers:
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