接受固定数量的参数,或者使用固定数量的默认值不接受参数

Accept fixed number of arguments, or no arguments using a fixed number of defaults

我想允许 2 个参数或 0 个参数并回退到默认值。我认为应该这样做

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('myargs', nargs=2, default=['foo', 'bar'])

但是,除了 2 个参数之外,这会抛出任何东西(因此永远不会调用默认值):

print(parser.parse_args(['a', 'b'])) # 2 arguments accepted
print(parser.parse_args([])) # throws

我的问题是,(如何)可以在没有额外代码的情况下完成。 IE。我想找到比这个解决方法更优雅、更默认的 argparse 的东西:

import argparse

def parse(args):

    parser = argparse.ArgumentParser()
    parser.add_argument('myargs', nargs='*', default=['foo', 'bar'])
    a = parser.parse_args(args)

    if len(a.myargs) != 2:
        raise IOError('Incorrect number of arguments')

    return a

print(parse([])) # defaults
print(parse(['a', 'b'])) # 2 arguments accepted
print(parse(['a', 'b', 'c'])) # throws (as excepted)

argparse 中,没有前缀的参数(默认前缀是 - 缩写或 -- 完整参数名称)被认为是强制性的。所以,如果你想有一个可选参数,你可以这样做:

parser = argparse.ArgumentParser()
parser.add_argument('--myargs', nargs=2, default=['foo', 'bar'])

在这种情况下,如果您不传递任何参数,它会像预期的那样工作:

print(parser.parse_args([]))

Namespace(myargs=['foo', 'bar'])

另一方面,如果您提供相同的值:

print(parser.parse_args(['--myargs', 'a', 'b']))

Namespace(myargs=['a', 'b'])

如果在 myargs:

后传递了错误数量的参数,将会引发错误
print(parser.parse_args(['--myargs', 'a']))

usage: scratch_2.py [-h] [--myargs MYARGS MYARGS]
<your script name>: error: argument --myargs: expected 2 arguments

另一种(更长的)方法是定义一个自定义操作来解析参数:

class CustomParsePositional(argparse.Action):
    """Action to parse arguments with a custom processing"""
    def __init__(self, option_strings, dest, nargs=None, **kwargs):
        super().__init__(option_strings, dest, nargs='*', **kwargs)
        self._custom_nargs = len(kwargs.get('default', []))
    def __call__(self, parser, namespace, values, option_string=None):
        if len(values) != self._custom_nargs:
            parser.error('Incorrect number of arguments')
        namespace.__setattr__(self.dest, values)

parser = argparse.ArgumentParser()
parser.add_argument('myargs', default=['foo', 'bar'], type=str, action=CustomParsePositional)

在这种情况下,预期值的数量由 add_argumentdefault 参数中的项目数量推断。这里有一些例子:

print(parser.parse_args(['a', 'b']))
print(parser.parse_args([]))
print(parser.parse_args(['a']))


Namespace(myargs=['a', 'b'])
Namespace(myargs=['foo', 'bar'])
usage: scratch_2.py [-h] [myargs [myargs ...]]
<your script name>: error: Incorrect number of arguments

如果您传递 3 个值,您也会得到一个错误:

print(parser.parse_args(['a', 'b', 'c']))

usage: scratch_2.py [-h] [myargs [myargs ...]]
<your script name>: error: Incorrect number of arguments

简要说明 argparse 解析的工作原理可能会有所帮助。

值收集在 namespace 对象中。开始解析时,default的值全部放在namespace中。然后,当在用户输入中遇到参数时,它们将按照 add_argument 中指定的方式进行解析,并将值放在 namespace 中,覆盖默认值。

当看到标志时,会解析带标志的参数,例如'--foo bar',但像你这样的位置是必需的,并采用 nargs 指定的确切字符串数。在这种情况下,它将使用 2 个字符串。所以对于这个定义,default参数是没有用的。

nargs='*',任意数量的字符串都满足。 0 字符串的情况得到特殊处理,并将默认值放在 namespace.

解析后添加检查没有什么不雅的地方。错误消息可以简化为:

if len(a.myargs) != 2:
    parser.error('Incorrect number of arguments')

optparse 这样的旧解析器处理了所有标记的参数,并在 extras 中返回其余的供您处理。 argparse 处理那些 extras。使用固定 nargs 处理位置是最简单的。一个带有变量 nargs 的位置通常可以正常工作,但很难使用多个。如果您使用 * 指定了两个操作,解析器应该如何分配字符串?

是的,您可以定义自定义 Action 类 来处理特殊情况,但通常结果不如更简单的 post-解析检查那么优雅 (IMO)。我不给聪明加分:)