【问题标题】:'argparse' with optional positional arguments that start with dash'argparse' 带有以破折号开头的可选位置参数
【发布时间】:2018-04-22 15:52:21
【问题描述】:

我们正在尝试在我们正在使用的命令行工具上构建一个包装脚本。我们想根据包装脚本中的选项设置一些工具参数。我们还希望能够将本地参数直接传递给命令行工具,因为它们是写在命令行上的。

这是我们想出的:

import argparse

parser = argparse.ArgumentParser()

parser.add_argument('positional')
parser.add_argument('-f', '--foo', action='store_true')
parser.add_argument('-b', '--bar', action='store_true')

parser.add_argument('native_arg', nargs='*')

args = parser.parse_args()
print (args)

positional 是必填项。基于选项-f-b,我们将在我们的工具调用中添加一些额外的选项。之后留下的任何东西(如果有的话)都应该被视为本机工具参数并直接提供给工具。使用-h 调用我们的脚本会产生以下用法:

usage: test.py [-h] [-f] [-b] positional [native_arg [native_arg ...]]

诀窍在于,这些本机参数本身就是工具的选项,并且包含前导破折号,例如 -native0-native1。我们已经知道使用双破折号阻止 argparse 寻找更多选项的技巧。以下调用:

./test.py pos -- -native0 -native1

产生预期的解析参数:

Namespace(bar=False, foo=False, native_arg=['-native0', '-native1'], positional='pos')

但是,尝试在第一个位置参数之后添加选项不起作用。更具体地说,以下调用:

./test.py pos --foo -- -native0 -native1

产生以下输出:

usage: [...shortened...]
test.py: error: unrecognized arguments: -- -native0 -native1

将可选参数放在位置之前:

./test.py --foo pos -- -native0 -native1

似乎可以工作,因为打印了以下内容:

Namespace(bar=False, foo=True, native_arg=['-native0', '-native1'], positional='pos')

更奇怪的是,将 native_argnargs 的值更改为 '+' 在上述所有情况下都有效(当然需要注意的是,至少需要一个 native_arg)。

我们是在 Python 代码中做错了什么还是这是某种 argparse 错误?

【问题讨论】:

    标签: python argparse


    【解决方案1】:

    argparse 确实很难将非必需的位置参数与可选参数混合使用(有关错误报告的详细信息,请参阅https://stackoverflow.com/a/47208725/1399279)。我不会提出解决此问题的方法,而是提出一种替代方法。

    您应该查看 parse_known_args 方法,该方法是为您描述的情况创建的(即将选项传递给包装工具)。

    In [1]: import argparse
    
    In [2]: parser = argparse.ArgumentParser()
    
    In [3]: parser.add_argument('positional')
    
    In [4]: parser.add_argument('-f', '--foo', action='store_true')
    
    In [5]: parser.add_argument('-b', '--bar', action='store_true')
    
    In [6]: parser.parse_known_args(['pos', '--foo', '-native0', '-native1'])
    Out[6]: (Namespace(bar=False, foo=True, positional='pos'), ['-native0', '-native1'])
    

    parse_args 不同,parse_known_args 的输出是一个二元素元组。第一个元素是您希望从parse_args 获得的Namespace 实例,它包含调用add_argument 定义的所有属性。第二个元素是解析器不知道的所有参数的列表。

    我个人更喜欢这种方法,因为用户不需要记住任何关于如何调用程序的技巧,或者哪个选项顺序不会导致错误。

    【讨论】:

      【解决方案2】:

      这是一个已知问题(https://bugs.python.org/issue15112, argparse: nargs='*' 位置参数如果前面有一个选项和另一个位置,则不接受任何项目

      解析交替处理位置和可选。在处理位置时,它会尝试处理输入字符串所需的数量。但是?* 位置满足[],一个空的字符串列表。另一方面,+ 至少需要一个字符串

      ./test.py pos --foo -- -native0 -native1
      

      解析器将“pos”提供给positional,将[] 提供给native-arg。然后它把'--foo' 赋予它的可选项。剩下的字符串已经没有 positionals 了,所以它会引发错误。

      输入字符串的分配是通过regex字符串匹配的程式化形式完成的。想象一下匹配一个看起来像 AA? 的模式。

      要纠正这个问题,解析器必须向前看,并延迟处理native-arg。我们提出了补丁建议,但尚未投入生产。

      @SethMMorton 建议使用parse_known_args 是一个很好的建议。

      早期的解析器(例如 Optparse)处理所有标记的参数,但返回其余的位置,作为未区分的列表。由用户来拆分该列表。 argparse 增加了命名和解析positionals 的能力,但该算法在固定nargs 时效果最佳,并且在变量nargs 太多时会变得不稳定。

      【讨论】:

      • 我也很乐意用某种方法只解析-- 之前的内容,然后将其余部分作为一个大块。 parse_known_args 会让看起来像脚本选项的东西溜进native_args。我已经考虑了一下,它可能可以通过两个链式子解析器的组合来解决。由于本机 args 也主要用于其他脚本,我认为使用始终以 -- 之前的位置结尾的解决方法是可以接受的。
      • 双破折号改变了像'-native0'这样的字符串的解释方式,但不改变它们分配给positionals的方式。还有一个nargs='...'REMAINDER,但我认为它有同样的分配问题。我可以稍后测试。
      • 我试过了,它在positional 首次出现后吃掉了所有东西。这意味着在test.py position --foo -- -native0 中,--foo 选项会聚集到native_args 中。
      • 当前开发版本中添加了相关补丁,提供parsed_intermixed_args
      • REMAINDER 行为的发生出于同样的原因 - 参数在第一个位置之后立即处理。
      猜你喜欢
      • 1970-01-01
      • 2012-07-03
      • 2021-05-06
      • 1970-01-01
      • 2020-11-25
      • 1970-01-01
      • 2012-10-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多