如何创建具有多个位置参数的 argparse 互斥组?

Posted

技术标签:

【中文标题】如何创建具有多个位置参数的 argparse 互斥组?【英文标题】:How can I create an argparse mutually exclusive group with multiple positional parameters? 【发布时间】:2016-05-04 19:31:17 【问题描述】:

我正在尝试解析命令行参数,以使以下三种可能性成为可能:

script
script file1 file2 file3 …
script -p pattern

因此,文件列表是可选的。如果指定了-p pattern 选项,则命令行上不会有其他内容。以“用法”格式表示,它可能看起来像这样:

script [-p pattern | file [file …]]

我认为使用 Python 的 argparse 模块执行此操作的方法如下:

parser = argparse.ArgumentParser(prog=base)
group = parser.add_mutually_exclusive_group()
group.add_argument('-p', '--pattern', help="Operate on files that match the glob pattern")
group.add_argument('files', nargs="*", help="files to operate on")
args = parser.parse_args()

但是 Python 抱怨我的位置参数必须是可选的:

Traceback (most recent call last):
  File "script", line 92, in <module>
    group.add_argument('files', nargs="*", help="files to operate on")
…
ValueError: mutually exclusive arguments must be optional

但是argparse documentation 表示"*" 的参数nargs 意味着它是可选的。

我还没有找到任何其他可以解决问题的 nargs 值。我最接近的是使用nargs="?",但它只抓取一个文件,而不是任何数字的可选列表。

是否可以使用argparse 组合这种参数语法?

【问题讨论】:

有没有办法用“子解析器”来做到这一点? docs.python.org/2/library/argparse.html#sub-commands 不错的主意,@mac。子解析器似乎确实彼此独立运行。但是我对文档的快速阅读告诉我,调用哪个子解析器的选择是由较早的位置参数完成的。 (他们使用svn 命令的参数示例。)我没有任何可以在所需命令行参数中使用的选择机制。 是的,这似乎是一个主要限制;太糟糕了,不支持“默认”子解析器,当 args 不包含任何其他命名的子解析器时将使用该子解析器 【参考方案1】:

简短回答

default 添加到* 位置

引发错误的代码是,

    if action.required:
        msg = _('mutually exclusive arguments must be optional')
        raise ValueError(msg)

如果我在解析器中添加 *,我会看到 required 属性已设置:

In [396]: a=p.add_argument('bar',nargs='*')
In [397]: a
Out[397]: _StoreAction(option_strings=[], dest='bar', nargs='*', const=None, default=None, type=None, choices=None, help=None, metavar=None)
In [398]: a.required
Out[398]: True

而对于?,它将是 False。我将在代码中进一步挖掘以了解差异的原因。这可能是一个错误或被忽视的“功能”,或者可能有充分的理由。 '可选'位置的一个棘手的事情是没有答案是一个答案,也就是说,一个空的值列表是有效的。

In [399]: args=p.parse_args([])
In [400]: args
Out[400]: Namespace(bar=[], ....)

因此,mutual_exclusive 必须有某种方法来区分默认的[] 和真实的[]

如果您希望argparse 执行互斥测试,现在我建议使用--files,这是一个标记参数而不是位置参数。


设置位置的required属性的代码是:

    # mark positional arguments as required if at least one is
    # always required
    if kwargs.get('nargs') not in [OPTIONAL, ZERO_OR_MORE]:
        kwargs['required'] = True
    if kwargs.get('nargs') == ZERO_OR_MORE and 'default' not in kwargs:
        kwargs['required'] = True

所以解决方案是为*指定一个默认值

In [401]: p=argparse.ArgumentParser()
In [402]: g=p.add_mutually_exclusive_group()
In [403]: g.add_argument('--foo')
Out[403]: _StoreAction(option_strings=['--foo'], dest='foo', nargs=None, const=None, default=None, type=None, choices=None, help=None, metavar=None)
In [404]: g.add_argument('files',nargs='*',default=None)
Out[404]: _StoreAction(option_strings=[], dest='files', nargs='*', const=None, default=None, type=None, choices=None, help=None, metavar=None)
In [405]: p.parse_args([])
Out[405]: Namespace(files=[], foo=None)

默认值甚至可以是[]。解析器能够区分您提供的默认值和它使用的默认值(如果没有给出)。

哎呀——default=None 错了。它通过了add_argumentrequired 测试,但产生了mutual_exclusive 错误。细节在于代码如何区分用户定义的默认值和自动的默认值。所以使用除None之外的任何东西。

我在文档中没有看到任何关于此的内容。我将不得不检查错误/问题以查看该主题已被讨论过。以前可能也出现过。

【讨论】:

不错,但即使将default=None 添加到files 位置参数,如果我运行p.parse_args(["-p", "foo"])error: argument files: not allowed with argument -p/--pattern,我仍然会收到错误:error: argument files: not allowed with argument -p/--pattern 我通过default=[]获得了更大的成功 这完全成功了。将default=[] 添加到带有nargs="*" 的参数中,在所有情况下都会得到正确的结果。 使用None,您将收到mutual_exclusive 错误。这是它如何测试用户给定默认值和自动值的结果。所以除了None 之外的任何东西都是需要的。 我决定使用() 使默认值不可变。【参考方案2】:

您正在尝试使用“文件”参数来捕获许多文件,但您没有在 cmdline 示例中提供它。我认为图书馆会因为您没有使用破折号前缀而感到困惑。我建议如下:

import argparse

parser = argparse.ArgumentParser(prog="base")
group = parser.add_mutually_exclusive_group()
group.add_argument('-p', '--pattern', action="store", help="Operate on files that match the glob pattern")
group.add_argument('-f', '--files',  nargs="*", action="store", help="files to operate on")

args = parser.parse_args()
print args.pattern
print args.files

【讨论】:

我不想使用破折号前缀。我希望它们都是位置参数。【参考方案3】:
    import argparse
    parse  = argparse.ArgumentParser()
    parse.add_argument("-p",'--pattern',help="Operates on File")
    parse.add_argument("files",nargs = "*",help="Files to operate on")

    arglist = parse.parse_args(["-p","pattern"])
    print arglist
    arglist = parse.parse_args()
    print arglist
    arglist = parse.parse_args(["file1","file2","file3"])
    print arglist

【讨论】:

是的,但是arglist = parse.parse_args(["-p", "pattern", "file1","file2","file3"]) 应该给出一个错误,但它没有。 Namespace(files=['file1', 'file2', 'file3'], pattern='pattern') 它不会给出任何错误,因为可选参数“-p”默认采用一个参数,所以(pattern ='pattern') 和其余参数将分配给位置参数 (files = ['file1','file2','file3']) 没错。因为您跳过了互斥组,所以 argparse 不会像应有的那样为 -p pat file1 file2 file3 抛出错误。

以上是关于如何创建具有多个位置参数的 argparse 互斥组?的主要内容,如果未能解决你的问题,请参考以下文章

如何让 argparse 识别跟随可变长度可选参数的位置参数?

python argparse - 我可以只使用互斥的可选参数还是有更好的方法

如何在 argparse 中嵌套自定义参数解析器实例

argparse:如何允许带有 nargs="*" 和选项的空列表

如何使 argparse 将所有参数视为位置?

如何使用 2 个位置参数在 argparse 上打印帮助界面?