我可以指示 Python 的 argparse 将位置 arg 之后的所有参数也视为位置参数,即使它们匹配可选 arg 吗?

Can I direct Python's argparse to treat all arguments after a positional arg as also positional, even if they match an optional arg?

我正在编写一个实用程序(称之为 runner),作为其主要功能,运行 是另一个脚本。 运行 的脚本作为参数传递,以及应传递给脚本的任何参数。所以,例如:

runner script.py arg1 arg2

将 运行 script.py 作为子进程,以 arg1arg2 作为参数。这很简单,使用以下内容:

parser = argparse.ArgumentParser()

parser.add_argument("script", help="The script to run")
parser.add_argument("script_args", nargs="*", help="Script arguments", default=[])

args = parser.parse_args()

复杂的是我有一些额外的参数传递给 runner,它们修改了脚本的调用方式。其中之一是 --times,它将用于确定 运行 脚本的次数。所以:

runner --times 3 script.py arg1 arg2

将 运行 script.py 三次,以 arg1arg2 作为参数。

我要解决的问题是这样的。我希望脚本名称之后的任何参数都被视为传递给脚本的参数,即使它们匹配runner[=54=的参数名称].所以:

runner script.py arg1 arg2 --times 3

应该运行script.py一次,并传递参数arg1arg2--times3 到 script.py.

我找不到任何方法告诉 ArgumentParser 将 script arg 之后的所有参数都视为位置参数。

我知道脚本的调用者可以使用 -- special argument 强制执行此行为,因此以下将产生所需的行为:

runner -- script.py arg1 arg2 --times 3

但我希望程序改为处理此问题。这种行为可能吗?

首先,你不应该这样做。更清楚的是,如果您明确指出哪些参数适用于哪个程序(调用),也许您可​​以用引号将被调用脚本的整个脚本参数括起来,例如:

runner script.py "arg1 arg2 --times 3"

但如果您想按自己的方式执行此操作,则可以用自己的函数覆盖 ArgumentParser.parse_arguments 函数。我只是建立一个例子来说明它是如何可能的(对我来说它正在工作),但我不知道是否有任何副作用:

import argparse
from typing import Optional, Sequence


class ErrorCatchingArgumentParser(argparse.ArgumentParser):

    def parse_args(self, args: Optional[Sequence[str]] = ...) -> argparse.Namespace:
        ns, a = super().parse_known_args(args=args)
        args.insert(args.index(ns.script), "--")

        return super().parse_args(args=args)


if __name__ == '__main__':
    arguments = ["--time", "3", "myscript", "my_args", "myargs2", "--time", "5"]

    parser = ErrorCatchingArgumentParser()

    parser.add_argument("script", help="The script to run")
    parser.add_argument("--time", help="The script to run")
    parser.add_argument("script_args", nargs="*", help="Script arguments", default=[])

    print(arguments)
    namespace = parser.parse_args(arguments)

    print(arguments)
    print(namespace)

输出:

['--time', '3', 'myscript', 'my_args', 'myargs2', '--time', '5']
['--time', '3', '--', 'myscript', 'my_args', 'myargs2', '--time', '5']
Namespace(script='myscript', time='3', script_args=['my_args', 'myargs2', '--time', '5'])

  1. 使用超类的 parse_known_args 解析给定的参数以获得所有参数集(并且不引发错误)
  2. 搜索您的 script 变量并在参数列表中在其前面插入 --
  3. 使用超类的 parse_args 函数解析参数以获得“真实”参数

在输出中您可以看到原始参数、操作参数和创建的命名空间。

但我认为无论如何你都不应该这样做。