如何让 argparse 使用选项而不是前缀从文件中读取参数

Posted

技术标签:

【中文标题】如何让 argparse 使用选项而不是前缀从文件中读取参数【英文标题】:how to get argparse to read arguments from a file with an option rather than prefix 【发布时间】:2015-02-10 13:18:39 【问题描述】:

我想知道如何使用 python 的 argparse 模块从命令行和可能的文本文件中读取参数。我知道 argparse 的 fromfile_prefix_chars 但这并不是我想要的。我想要这种行为,但我不想要语法。我想要一个看起来像这样的界面:

$ python myprogram.py --foo 1 -A somefile.txt --bar 2

当 argparse 看到 -A 时,它应该停止从 sys.argv 或我给它的任何内容中读取,并调用我编写的函数来读取 somefile.text 并返回参数列表。当文件用完时,它应该继续解析 sys.argv 或其他什么。文件中参数的处理顺序是很重要的(即:-foo应该被处理,然后是文件中的参数,然后是-bar,这样文件中的参数可以覆盖--foo,并且-- bar 可能会覆盖文件中的内容)。

这样的事情可能吗?我可以编写一个自定义函数,将新参数推送到 argparse 的堆栈中,或者类似的东西吗?

【问题讨论】:

在您的版本中,什么会触发参数的特殊处理(而不是前缀字符)? @martineau:“-A”选项表示“下一个参数是要读取的文件”。我需要能够编写自己的函数来读取该文件并返回参数(它不是每行一个参数的格式) 查看source of _parse_known_args,看起来 argparse 喜欢预先知道所有参数。如果您设置了@fromfile_prefix_chars,那么它会查看参数并构建一个全新的列表来解析(_read_args_from_files)。我认为您最好的选择是提前解析 sys.argv 以获取参数列表。 你如何告诉参数解析器你有一个应该以这种方式处理的参数?我在想也许你可以继承argparse.ArgumentParser @mgilson:我从来没有说过我认为这是不合理的......只是想了解 OP 的想法。 【参考方案1】:

您可以使用自定义argparse.Action 来解决此问题,该argparse.Action 打开文件,解析文件内容,然后添加参数。

例如,这将是一个非常简单的操作:

class LoadFromFile (argparse.Action):
    def __call__ (self, parser, namespace, values, option_string = None):
        with values as f:
            # parse arguments in the file and store them in the target namespace
            parser.parse_args(f.read().split(), namespace)

你可以这样使用:

parser = argparse.ArgumentParser()
# other arguments
parser.add_argument('--file', type=open, action=LoadFromFile)
args = parser.parse_args()

args 中生成的命名空间还将包含同样从文件加载的任何配置,其中文件包含与命令行相同语法的参数(例如 --foo 1 --bar 2)。

如果您需要更复杂的解析,您也可以先单独解析文件内配置,然后有选择地选择应该接管哪些值。例如,由于参数是按照指定的顺序进行评估的,因此防止文件中的配置覆盖已在命令行上明确指定的值可能是有意义的。这将允许使用默认配置文件:

def __call__ (self, parser, namespace, values, option_string=None):
    with values as f:
        contents = f.read()

    # parse arguments in the file and store them in a blank namespace
    data = parser.parse_args(contents.split(), namespace=None)
    for k, v in vars(data).items():
        # set arguments in the target namespace if they haven’t been set yet
        if getattr(namespace, k, None) is not None:
            setattr(namespace, k, v)

当然,你也可以让文件读取复杂一些,比如先从 JSON 读取。

【讨论】:

啊哈!关键是要意识到解析器被传递给自定义操作,并且您可以使用该解析器来处理文件的内容。谢谢。 但是如果解析器在全局环境中有不同的名称,它仍然可以在这里使用。所以任何解析器都可以在这里使用。作为参数传递的parser 没有什么特别之处。 唉,如果您的任何参数设置为必需,这将失败。 @RovshanMusayev 文件格式与您提供给命令行的格式相同。例如。 --foo 1 --bar 2。但是您可以根据自己的喜好自定义操作中的逻辑以支持任何类型的格式。 非常感谢。这很有帮助。【参考方案2】:

你评论说

我需要能够编写自己的函数来读取该文件并返回参数(它不是每行一个参数的格式)–

现有的前缀文件处理程序中有一项规定可以更改文件的读取方式。该文件由“私有”方法parser._read_args_from_files 读取,但它调用一个简单的公共方法,将行转换为字符串,默认为每行一个参数操作:

def convert_arg_line_to_args(self, arg_line):
    return [arg_line]

它是这样编写的,因此您可以轻松自定义它。 https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.convert_arg_line_to_args

此方法的一个有用的替代方法是将每个空格分隔的单词视为一个参数:

def convert_arg_line_to_args(self, arg_line):
    for arg in arg_line.split():
        if not arg.strip():
            continue
        yield arg

test_argparse.py unittesting 文件中,有一个用于此替代方案的测试用例。


但如果您仍想使用参数选项而不是前缀字符来触发此读取,那么自定义 Action 方法是一个不错的方法。

您可以编写自己的函数来处理argv,然后再将其传递给parser。可以仿照parser._read_args_from_files

所以你可以编写如下函数:

def read_my_file(argv):
    # if there is a '-A' string in argv, replace it, and the following filename
    # with the contents of the file (as strings)
    # you can adapt code from _read_args_from_files
    new_argv = []
    for a in argv:
        ....
        # details left to user
    return new_argv

然后调用你的解析器:

parser.parse_args(read_my_file(sys.argv[1:]))

是的,这可以封装在 ArgumentParser 子类中。

【讨论】:

【参考方案3】:

Action 在调用时会在其参数中获得 parsernamespace

所以你可以把你的文件通过前者来更新后者:

class ArgfileAction(argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
        extra_args = <parse_the_file>(values)
        #`namespace' is updated in-place when specified
        parser.parse_args(extra_args,namespace)

【讨论】:

我相信 OP 已经知道此功能,如“我知道 argparse 的 fromfile_prefix_chars 但这不是我想要的”语句所示。

以上是关于如何让 argparse 使用选项而不是前缀从文件中读取参数的主要内容,如果未能解决你的问题,请参考以下文章

是否可以在 Python 中使用 Windows 样式的命令行选项?

在 argparse 中从变量而不是命令行获取值

python argparse:如何在错误时自动显示帮助?

如何获取argparse的帮助内容,而不是打印到控制台

如何测试我的代码是不是引发了适当的 argparse 异常?

如何使用 argparse 实现以下功能?