解析非互斥的命令行参数组

Parsing non-mutually-exclusive groups of command-line arguments

我正在尝试找到一种解析相关参数序列的方法,最好使用 argparse

例如:

command --global-arg --subgroup1 --arg1 --arg2 --subgroup2 --arg1 --arg3 --subgroup3 --arg4 --subcommand1 --arg1 --arg3

其中 --global-arg 适用于整个命令,但每个 --subgroupN 参数都有仅适用于它的子参数(并且可能具有相同的名称,例如 --arg1--arg3 以上),并且其中一些子参数是可选的,因此子参数的数量不是常数。但是,我知道每个 --subgroupN 子参数集都是通过另一个 --subgroupN 的存在或参数列表的末尾来完成的(如果全局参数不能出现在末尾,我不会大惊小怪,尽管我想这是可能的,只要它们不与子参数名称冲突。

--subgroupN 元素本质上是子命令,但我似乎无法使用 argparse 的子解析器能力,因为它会吞噬任何后续 --subgroupN 条目以及(因此带有意外参数的 barfs)。

(xmlstarlet 使用了这种样式的参数列表示例)

除了编写我自己的解析器之外,还有什么建议吗?如果这是唯一的选择,我想我至少可以利用 argparse 中的一些东西...

例子

下面的示例试图找到一种方法来按照以下几行解析参数结构:

(a --name <name>|b --name <name>)+

在第一个示例中,我希望 --a 和 --b 引入一组由子解析器处理的参数。

我希望能从

中得到一些东西
Namespace(a=Namespace(name="dummya"), b=Namespace(name="dummyb"))

子解析器示例失败

parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
parser_a = subparsers.add_parser("a")
parser_b = subparsers.add_parser("b")
parser_a.add_argument("--name")
parser_b.add_argument("--name")
parser.parse_args(["a", "--name", "dummy"])
> Namespace(name='dummy') (Good)
parser.parse_args(["b", "--name", "dummyb", "a", "--name", "dummya"])
> error: unrecognized arguments: a (BAD)

互斥组失败

parser = argparse.ArgumentParser()
g = parser.add_mutually_exclusive_group()
g1 = g.add_mutually_exclusive_group()
g1.add_argument("--name")
g2 = g.add_mutually_exclusive_group()
g2.add_argument("--name")
> ArgumentError: argument --name: conflicting option string(s): --name (BAD)

(我并不是真的希望它能起作用,它只是想看看我是否可以重复分组参数。)

除子解析器机制外,argparse 不是为处理参数组而设计的。除了 nargs 分组之外,它按照参数在 argv 列表中出现的顺序处理参数。

正如我在评论中提到的那样,之前有一些问题,可以通过搜索 multiple 之类的词找到。但是他们试图以某种方式研究 argparse.

的基本 order-independent 设计

https://whosebug.com/search?q=user%3A901925+[argparse]+multiple

我认为最直接的解决方案是事先处理 sys.argv 列表,将其分成几组,然后将这些子列表传递给一个或多个 parsers

parse [command --global-arg], 
parse [--subgroup1 --arg1 --arg2], 
parse [--subgroup2 --arg1 --arg3], 
parse [--subgroup3 --arg4], 
parse [--subcommand1 --arg1 --arg3]

事实上,唯一的选择是使用该子解析器 'slurp everything else' 行为来获取可以再次解析的剩余参数。使用 parse_known_args 到 return 未知参数列表(如果该列表不为空,parse_args 会引发错误)。

利用上面的,我得出以下结论:

args = [
    "--a", "--name", "dummya", 
    "--b", "--name", "dummyb",
    "--a", "--name", "another_a", "--opt"
]
parser_globals = argparse.ArgumentParser()
parser_globals.add_argument("--test")

parser_a = argparse.ArgumentParser()
parser_a.add_argument("--name")
parser_a.add_argument("--opt", action="store_true")

parser_b = argparse.ArgumentParser()
parser_b.add_argument("--name")

command_parsers = {
    "--a": parser_a,
    "--b": parser_b
}

the_namespace = argparse.Namespace()
if globals is not None:
    (the_namespace, rest) = parser_globals.parse_known_args(args)

subcommand_dict = vars(the_namespace)
subcommand = []
val = rest.pop()
while val:
    if val in command_parsers:
        the_args = command_parsers[val].parse_args(subcommand)
        if val in subcommand_dict:
            if "list" is not type(subcommand_dict[val]):
                subcommand_dict[val] = [subcommand_dict[val]]
            subcommand_dict[val].append(the_args)
        else:
            subcommand_dict[val] = the_args
        subcommand = []
    else:
        subcommand.insert(0, val)
    val = None if not rest else rest.pop()

我最终得到:

Namespace(
    --a=[
        Namespace(
            name='another_a',
            opt=True
        ),
        Namespace(
            name='dummya',
            opt=False
        )
    ],
    --b=Namespace(
        name='dummyb'
    ),
    test=None
)

这似乎符合我的目的。