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