Argparse:如何处理变量数量的参数(nargs='*')

时间:2021-01-20 23:22:36

I thought that nargs='*' was enough to handle a variable number of arguments. Apparently it's not, and I don't understand the cause of this error.

我认为nargs='*'足够处理变量数量的参数。显然不是,我不明白这个错误的原因。

The code:

代码:

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

I think the resulting namespace should be Namespace(pos='1', foo='2', spam='8', vars=['8', '9']). Instead, argparse gives this error:

我认为结果的命名空间应该是命名空间(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

Basically, argparse doesn't know where to put those additional arguments... Why is that?

基本上,argparse不知道把那些额外的参数放在哪里……这是为什么呢?

2 个解决方案

#1


26  

The relevant Python bug is Issue 15112.

相关的Python bug是第15112期。

argparse: nargs='*' positional argument doesn't accept any items if preceded by an option and another positional

如果前面有一个选项和另一个位置参数,那么nargs='*'位置参数不接受任何项

When argparse parses ['1', '2', '--spam', '8', '8', '9'] it first tries to match ['1','2'] with as many of the positional arguments as possible. With your arguments the pattern matching string is AAA*: 1 argument each for pos and foo, and zero arguments for vars (remember * means ZERO_OR_MORE).

当argparse将['1','2',' -spam', '8', '8', '9']解析时,它首先尝试将['1','2']与尽可能多的位置参数匹配起来。在您的参数中,模式匹配字符串为AAA*: 1参数为pos和foo,而vars的参数为0(记住*意味着ZERO_OR_MORE)。

['--spam','8'] are handled by your --spam argument. Since vars has already been set to [], there is nothing left to handle ['8','9'].

['-垃圾邮件','8']由你的-垃圾邮件论点来处理。由于vars已经被设置为[],所以没有什么可以处理['8','9']。

The programming change to argparse checks for the case where 0 argument strings is satisfying the pattern, but there are still optionals to be parsed. It then defers the handling of that * argument.

当0个参数字符串满足模式时,编程更改为argparse检查,但仍有可解析的选项。然后,它推迟了对该*参数的处理。

You might be able to get around this by first parsing the input with parse_known_args, and then handling the remainder with another call to parse_args.

您可以先使用parse_known_args解析输入,然后再调用parse_args来处理其余的部分,从而解决这个问题。

To have complete freedom in interspersing optionals among positionals, in issue 14191, I propose using parse_known_args with just the optionals, followed by a parse_args that only knows about the positionals. The parse_intermixed_args function that I posted there could be implemented in an ArgumentParser subclass, without modifying the argparse.py code itself.

在第14191期中,为了在位置选择项之间有完全的*,我建议使用parse_known_args,只使用选项项,然后使用parse_args只知道位置项。我在那里发布的parse_intermixed_args函数可以在ArgumentParser子类中实现,而无需修改argparse。py代码本身。


Here's a way of handling subparsers. I've taken the parse_known_intermixed_args function, simplified it for presentation sake, and then made it the parse_known_args function of a Parser subclass. I had to take an extra step to avoid recursion.

这里有一种处理子解析器的方法。我将parse_known_intermixed_args函数简化为表示形式,然后将其设置为解析器子类的parse_known_args函数。为了避免递归,我必须采取额外的步骤。

Finally I changed the _parser_class of the subparsers Action, so each subparser uses this alternative parse_known_args. An alternative would be to subclass _SubParsersAction, possibly modifying its __call__.

最后,我更改了subparsers操作的_parser_class,因此每个子解析器都使用这个替代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


8  

Simple solution: Specify the --spam flag before specifying pos and foo:

简单解决方案:在指定pos和foo之前指定—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('--spam 8 1 2 8 9'.split())

The same works if you place the --spam flag after specifying your variable arguments.

如果在指定变量参数后放置-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: For what it's worth, it seems that changing the * to a + will also fix the error.

编辑:就其价值而言,似乎将*改成+也会修复错误。

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

#1


26  

The relevant Python bug is Issue 15112.

相关的Python bug是第15112期。

argparse: nargs='*' positional argument doesn't accept any items if preceded by an option and another positional

如果前面有一个选项和另一个位置参数,那么nargs='*'位置参数不接受任何项

When argparse parses ['1', '2', '--spam', '8', '8', '9'] it first tries to match ['1','2'] with as many of the positional arguments as possible. With your arguments the pattern matching string is AAA*: 1 argument each for pos and foo, and zero arguments for vars (remember * means ZERO_OR_MORE).

当argparse将['1','2',' -spam', '8', '8', '9']解析时,它首先尝试将['1','2']与尽可能多的位置参数匹配起来。在您的参数中,模式匹配字符串为AAA*: 1参数为pos和foo,而vars的参数为0(记住*意味着ZERO_OR_MORE)。

['--spam','8'] are handled by your --spam argument. Since vars has already been set to [], there is nothing left to handle ['8','9'].

['-垃圾邮件','8']由你的-垃圾邮件论点来处理。由于vars已经被设置为[],所以没有什么可以处理['8','9']。

The programming change to argparse checks for the case where 0 argument strings is satisfying the pattern, but there are still optionals to be parsed. It then defers the handling of that * argument.

当0个参数字符串满足模式时,编程更改为argparse检查,但仍有可解析的选项。然后,它推迟了对该*参数的处理。

You might be able to get around this by first parsing the input with parse_known_args, and then handling the remainder with another call to parse_args.

您可以先使用parse_known_args解析输入,然后再调用parse_args来处理其余的部分,从而解决这个问题。

To have complete freedom in interspersing optionals among positionals, in issue 14191, I propose using parse_known_args with just the optionals, followed by a parse_args that only knows about the positionals. The parse_intermixed_args function that I posted there could be implemented in an ArgumentParser subclass, without modifying the argparse.py code itself.

在第14191期中,为了在位置选择项之间有完全的*,我建议使用parse_known_args,只使用选项项,然后使用parse_args只知道位置项。我在那里发布的parse_intermixed_args函数可以在ArgumentParser子类中实现,而无需修改argparse。py代码本身。


Here's a way of handling subparsers. I've taken the parse_known_intermixed_args function, simplified it for presentation sake, and then made it the parse_known_args function of a Parser subclass. I had to take an extra step to avoid recursion.

这里有一种处理子解析器的方法。我将parse_known_intermixed_args函数简化为表示形式,然后将其设置为解析器子类的parse_known_args函数。为了避免递归,我必须采取额外的步骤。

Finally I changed the _parser_class of the subparsers Action, so each subparser uses this alternative parse_known_args. An alternative would be to subclass _SubParsersAction, possibly modifying its __call__.

最后,我更改了subparsers操作的_parser_class,因此每个子解析器都使用这个替代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


8  

Simple solution: Specify the --spam flag before specifying pos and foo:

简单解决方案:在指定pos和foo之前指定—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('--spam 8 1 2 8 9'.split())

The same works if you place the --spam flag after specifying your variable arguments.

如果在指定变量参数后放置-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: For what it's worth, it seems that changing the * to a + will also fix the error.

编辑:就其价值而言,似乎将*改成+也会修复错误。

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