【问题标题】:How do I parse argparse arguments into a list of strings, stopping on a predefined flag argument?如何将 argparse 参数解析为字符串列表,在预定义的标志参数处停止?
【发布时间】:2012-10-22 01:49:31
【问题描述】:

如何解析由特殊预定义语法分隔的可变长度参数列表。一个例子:

   ./script --arg1 --cmdname otherscript --a1 --a2 --cmdname-- --arg3

argparse解析后脚本应该有三个参数:arg1cmdnamearg3。参数cmdname 应包含三个值的列表otherscripta1a2

拥有这样的配方对于能够将cmdname 中的所有内容传递到subprocess.popen(cmdname, ...) 调用中会很有用。

我在考虑子解析器。但我相信子解析器无法停止,并且与其他子解析器确实是互斥的。还有其他简单的,已经提供的方法吗?将Action 子类化是一种方法吗?

【问题讨论】:

  • 这种替代语法就足够了吗? ./script --arg1 --cmdname "otherscript --a1 --a2" --arg3
  • @unutbu -- 这是一个非常好的建议。将它与shlex.split 配对,我认为你会做生意。
  • @unutbu 和@mgilson:好点!不知道shlex.split()。我建议的语法在 shell 引用和转义方面更具吸引力:如果您不必担心引用,传递带有参数的子命令会容易得多。

标签: python python-3.x argparse


【解决方案1】:

如果我们区分以-- 开头的参数和同样以-- 开头的命令部分,这将有助于 argparse。

所以如果./script ++arg1 ++cmdname otherscript --a1 --a2 ++arg3 是可以接受的,那么:

import argparse
import shlex

parser = argparse.ArgumentParser(prefix_chars = '+')
parser.add_argument('++arg1', action = 'store_true')
parser.add_argument('++arg3', action = 'store_true')
parser.add_argument('++cmdname', nargs = '*')
args = parser.parse_args(shlex.split("++arg1 ++cmdname otherscript --a1 --a2 ++arg3")) 
print(args)

产量

Namespace(arg1=True, arg3=True, cmdname=['otherscript', '--a1', '--a2'])

【讨论】:

  • 聪明。 +1 这个答案和(更重要的是你之前的评论,我仍然认为这是最好的方法)
  • @mgilson:是的,这就是像bash -c "..." 这样的其他命令的工作方式,所以它可能是程序员/用户最熟悉的。
  • 我想这种方法的缺点是如果otherscript 使用+,因为它也是“prefix_chars”。不过这种情况很少见,所以应该很安全。
  • @unutbu:不幸的是,@mgilson 之前的评论是这种方法的限制因素。虽然我通常同意明确分离概念,但 otherscript 及其选项超出了我的控制范围,而且我计划支持的一些脚本确实具有非常混合的语法。事实上,他们对更多子命令使用相同的技巧。这种方法的美妙之处在于,一旦编写了解析器,就可以嵌套和混合。其他脚本使用 perl(和他们自己的解析器,没有 std 模块),所以我不能复制和粘贴;-)
【解决方案2】:

正如您在帖子中指出的那样,子类化 Action 可能是这样做的方法——尽管如果 argparse 不知道 otherscript 的参数,这会变得非常棘手。您可能可以使用parse_known_args 解决此问题,但您可能无法解决。老实说,我真的认为最简单的方法是自己预处理sys.argv

import shlex
s = shlex.split("./script --arg1 --cmdname otherscript --a1 --a2 --cmdname-- --arg3")
def preprocess(lst):
    """
    process an iterable into 2 lists.
    The second list contains the portion bracketed by '--cmdname' and '--cmdname--'
    whereas the first portion contains the rest of it.
    """
    argv1,argv2 = [],[]
    current = argv1
    for i in lst:
        if i == '--cmdname':
           current = argv2
        elif i == '--cmdname--':
           current = argv1
        else:
           current.append(i)
    return argv1,argv2

l1,l2 = preprocess(s)
print l1
print l2

preprocess 的替代实现适用于具有 .index 方法的可切片对象 -- sys.argv 可以正常工作:

def preprocess(lst):
    """
    process an iterable into 2 lists.
    The second list contains the portion bracketed by '--cmdname' and '--cmdname--'
    whereas the first portion contains the rest of it.
    """
    try:
        i1 = lst.index('--cmdname')
        i2 = lst.index('--cmdname--')
        argv1 = lst[i1+1:i2]
        argv2 = lst[:i1]+lst[i2+1:]
    except ValueError:
        argv1 = lst
        argv2 = []

    return argv1,argv2

另一个选择(在@unutbu 的出色评论中指出)是将命令行语法更改为更标准的东西,这大大简化了问题:

./script --arg1 --cmd "otherscript --a1 --a2" --arg3

然后您可以像通常使用argparse 一样解析cmd(为此参数指定type=shlex.split 以从字符串转换为参数列表)。

【讨论】:

  • 这是一个很好的务实方法。这可以与将l1 传递给argparse.parse_args() 结合使用。不过,我更喜欢集成的 argparse 解决方案。我并不关注确切的 --cmdname-- 停止标志。我猜 ++cmdname 或者甚至 --cmdname 如果这有助于留在 argparse 领域内,那也可以。
  • @cfi -- 是的,我假设你会将l1 传递给parser.parse_args()。我真的看不出你会比 unutbu 的建议做得更好,但如果你想完全留在 argparse 中(至少,如果你不想深入了解内部结构并依赖一堆实现细节) .问题是 argparse 会将--a1 解释为一个参数,并且它会使用相关的操作,或者抱怨它不知道如何处理该参数。
  • 解决问题 -> 接受。虽然如果有人能想出一个集成的 argparse 解决方案,我更喜欢它,因为它更清晰、易于使用,可能还有自动使用 --help 生成。
  • 研究了子类化。即使_parse_known_args 也必须撕开并修改其他几个地方。 argparse 不提供用于子类化/更改标记器的接口,同时保持一致的行为(帮助页面!)。这个项目的时间有限,所以我必须继续前进。感谢你们@unutbu 和@mgilson!
  • 请将IndexError 重命名为ValueError,由str.index() 抛出(此处为Python 3.2.2)
猜你喜欢
  • 2021-07-13
  • 1970-01-01
  • 1970-01-01
  • 2012-02-11
  • 2020-04-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-04
相关资源
最近更新 更多