Подтвердить что ты не робот

Argparse: как обрабатывать переменное количество аргументов (nargs = '*')

Я думал, что nargs='*' достаточно для обработки переменного количества аргументов. По-видимому, это не так, и я не понимаю причину этой ошибки.

Код:

p = argparse.ArgumentParser()
p.add_argument('pos')
p.add_argument('foo')
p.add_argument('--spam', default=24, type=int, dest='spam')
p.add_argument('vars', nargs='*')

p.parse_args('1 2 --spam 8 8 9'.split())

Я думаю, что получившееся пространство имен должно быть Namespace(pos='1', foo='2', spam='8', vars=['8', '9']). Вместо этого argparse дает эту ошибку:

usage: prog.py [-h] [--spam SPAM] pos foo [vars [vars ...]]
error: unrecognized arguments: 9 8

В принципе, argparse не знает, куда помещать эти дополнительные аргументы... Почему это?

4b9b3361

Ответ 1

Соответствующая ошибка Python Проблема 15112.

argparse: nargs='*' аргумент positional не принимает никаких элементов, если ему предшествует опция и другой позиционный

Когда argparse анализирует ['1', '2', '--spam', '8', '8', '9'], он сначала пытается сопоставить ['1','2'] с максимально возможным количеством позиционных аргументов. С вашими аргументами строка соответствия шаблону AAA*: 1 аргумент для pos и foo, а нулевые аргументы для vars (помните * означает ZERO_OR_MORE).

['--spam','8'] обрабатываются вашим аргументом --spam. Поскольку vars уже установлен в [], обрабатывать ['8','9'] нечего.

Изменение программирования на argparse проверяет случай, когда строки аргументов 0 удовлетворяют шаблону, но все еще optionals обрабатывается. Затем он отменяет обработку этого аргумента *.

Возможно, вам удастся обойти это, сначала проанализировав вход с помощью parse_known_args, а затем обработав remainder другим вызовите parse_args.

Чтобы иметь полную свободу в вкрапленных опциях из позиций, в issue 14191, я предлагаю использовать parse_known_args только с optionals, а затем parse_args, который знает только о позициях. Функция parse_intermixed_args, которую я опубликовал там, может быть реализована в подклассе ArgumentParser без изменения самого кода argparse.py.


Вот способ обработки подпараметров. Я принял parse_known_intermixed_args, упростил его ради представления, а затем сделал его функцией parse_known_args подкласса Parser. Мне пришлось сделать дополнительный шаг, чтобы избежать рекурсии.

Наконец, я изменил _parser_class подпарамера Action, поэтому каждый подпараметр использует эту альтернативу parse_known_args. Альтернативой может быть подкласс _SubParsersAction, возможно модифицирующий его __call__.

from argparse import ArgumentParser

def parse_known_intermixed_args(self, args=None, namespace=None):
    # self - argparse parser
    # simplified from http://bugs.python.org/file30204/test_intermixed.py
    parsefn = super(SubParser, self).parse_known_args # avoid recursion

    positionals = self._get_positional_actions()
    for action in positionals:
        # deactivate positionals
        action.save_nargs = action.nargs
        action.nargs = 0

    namespace, remaining_args = parsefn(args, namespace)
    for action in positionals:
        # remove the empty positional values from namespace
        if hasattr(namespace, action.dest):
            delattr(namespace, action.dest)
    for action in positionals:
        action.nargs = action.save_nargs
    # parse positionals
    namespace, extras = parsefn(remaining_args, namespace)
    return namespace, extras

class SubParser(ArgumentParser):
    parse_known_args = parse_known_intermixed_args

parser = ArgumentParser()
parser.add_argument('foo')
sp = parser.add_subparsers(dest='cmd')
sp._parser_class = SubParser # use different parser class for subparsers
spp1 = sp.add_parser('cmd1')
spp1.add_argument('-x')
spp1.add_argument('bar')
spp1.add_argument('vars',nargs='*')

print parser.parse_args('foo cmd1 bar -x one 8 9'.split())
# Namespace(bar='bar', cmd='cmd1', foo='foo', vars=['8', '9'], x='one')

Ответ 2

Простое решение: укажите флаг --spam перед тем, как указать pos и foo:

p = argparse.ArgumentParser()
p.add_argument('pos')
p.add_argument('foo')
p.add_argument('--spam', default=24, type=int, dest='spam')
p.add_argument('vars', nargs='*')

p.parse_args('--spam 8 1 2 8 9'.split())

То же самое работает, если вы поместите флаг --spam после указания аргументов переменной.

p = argparse.ArgumentParser()
p.add_argument('pos')
p.add_argument('foo')
p.add_argument('--spam', default=24, type=int, dest='spam')
p.add_argument('vars', nargs='*')

p.parse_args('1 2 8 9 --spam 8'.split())

EDIT: для того, что стоит, кажется, что изменение * на a + также устранит ошибку.

p = argparse.ArgumentParser()
p.add_argument('pos')
p.add_argument('foo')
p.add_argument('--spam', default=24, type=int, dest='spam')
p.add_argument('vars', nargs='+')

p.parse_args('1 2 --spam 8 8 9'.split())