2013-11-23 5 views
34

Я думал, что nargs='*' было достаточно для обработки переменного количества аргументов. По-видимому, это не так, и я не понимаю причину этой ошибки.Argparse: как обрабатывать переменное количество аргументов (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 не знает, куда девать эти дополнительные аргументы ... Почему?

ответ

25

Соответствующая ошибка Python: Issue 15112.

argparse: nargs='*' positional argument doesn't accept any items if preceded by an option and another 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.

Чтобы иметь полную свободу в пересыпая среди positionals УСТРОЙСТВА, в issue 14191, я предлагаю использовать parse_known_args только с optionals, а затем parse_args, который знает только о positionals. Функция 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') 
+0

О, это хорошая новость. Так вы говорите, что если я возьму две функции, которые вы добавили в http://bugs.python.org/file30422/intermixed.patch, и я реализую в своем подклассе, я смогу обойти проблему? Как насчет функции '_get_values ​​()', является ли необходимость в двух строках строго обязательной? Мне трудно понять, как используется «SUPPRESS». – rubik

+0

О, нет, плохие новости только что прибыли. Я читал, что это несовместимо с подпарщиками, особенностью, которая мне абсолютно необходима. Это временное или просто невозможно реализовать? Инстинктивно я бы сказал, что это должно быть возможно, потому что subparser указан как второй аргумент, тогда разбор происходит нормально. Я ошибаюсь? – rubik

+0

Я пробовал 2 разных способа временно отключить аргументы 'positional'. Первым было «nargs = 0». Второй - «nargs = SUPPRESS». Я использовал бы первый, если бы реализовал свой собственный подкласс. «SUPPRESS» имеет некоторые преимущества, но требует более глубоких изменений в 'argparse. – hpaulj

6

Простое решение: Укажите --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: Для чего это стоит, кажется, что изменение * к + также исправить ошибку.

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()) 
+4

Ну да, я мог бы это сделать, но это не то, что я хочу. Я создаю инструмент командной строки, поэтому я не тот, кто напишет аргументы. Я не могу навязать пользователю конкретное решение. Код, который я написал, должен работать и должен быть гибким. Я действительно не знаю, почему это терпит неудачу. – rubik

+1

Правда, но вполне возможно, что 'argparse' не может справиться с этой конкретной ситуацией. Также вполне возможно, что правила для синтаксического анализа аргументов не позволяют размещать аргументы таким образом. Во всяком случае, я изменил свой ответ с помощью другого потенциального решения, FWIW. – caleb531

+0

Вы правы, в конце концов он оказался ошибкой argparse, как описывает другой ответ. – rubik

Смежные вопросы