为什么Python argparse中的fromfile-prefix-chars不起作用?

时间:2014-08-01 16:44:23

标签: python python-2.7 argparse

我正在尝试在Python中使用argparse的fromfile-prefix-chars功能从文件加载我的所有命令行参数,但它一直在抱怨我没有指定一些参数。

代码:

import argparse

def go():
   parser = argparse.ArgumentParser(fromfile_prefix_chars='@')
   parser.add_argument("--option1")
   parser.add_argument("--option2", type=int, required=True)
   args = parser.parse_args()

if __name__ == "__main__":
    go()

参数文件:

--option1 foo
--option2 1234

命令行和输出:

$ python testargparse.py @testargs
usage: testargparse.py [-h] [--option1 OPTION1] --option2 OPTION2
testargparse.py: error: argument --option2 is required

你可以看到我在文件中提供了必需的参数,但是argparse没有看到它。

4 个答案:

答案 0 :(得分:6)

来自文档:

  

从文件读取的参数默认情况下必须是每行一个...并且被视为与命令行上的原始文件引用参数位于同一位置。因此,在上面的示例中,表达式[' -f',' foo',' @ args.txt']被视为等同于[&#39] ; -f',' foo',' -f',' bar']。

在示例中:

fp.write('-f\nbar')

所以该文件包含:

-f
bar

换句话说,每个文件行对应一个单词' (命名行中为空格)。 --option1=foo是一个词。 --option1 foo被解释为就像在命令行中引用一样,例如。 prog.py '--option1 foo' '--option2 1234'

https://docs.python.org/dev/library/argparse.html#argparse.ArgumentParser.convert_arg_line_to_args有一个自定义函数,可以在空格上分割线条。如果您想坚持使用参数文件,请尝试使用它。


import argparse

with open('args.txt', 'w') as fp:
    fp.write('--option1 foo\n--option2 1234')  # error
    # but works with modifed 'convert...'
    #fp.write('--option1=foo\n--option2=1234')  # works
    #fp.write('--option1\nfoo\n--option2\n1234') # works

def convert_arg_line_to_args(arg_line):
    for arg in arg_line.split():
        if not arg.strip():
            continue
        yield arg
"""
default line converter:
def convert_arg_line_to_args(self, arg_line):
    return [arg_line]
"""

def go():
   parser = argparse.ArgumentParser(fromfile_prefix_chars='@')
   parser.convert_arg_line_to_args = convert_arg_line_to_args
   parser.add_argument("--option1")
   parser.add_argument("--option2", type=int, required=True)
   args = parser.parse_args(['@args.txt'])
   print args

if __name__ == "__main__":
    go()

@toes建议使用shlex来解析文件。 shlex有一个很好的功能,它会删除不必要的引号。

shlex可用于分割文件的各行。

def sh_split(arg_line):
    for arg in shlex.split(arg_line):
        yield arg
parser.convert_arg_line_to_args = sh_split

或者它可以替换整个@file读取方法(_read_args_from_files) - 这应该与@toes回答相同,除了@file字符串可以在命令行中的任何位置(甚至重复)。

def at_read_fn(arg_strings):
    # expand arguments referencing files
    new_arg_strings = []
    for arg_string in arg_strings:
        if not arg_string or not arg_string.startswith('@'):
            new_arg_strings.append(arg_string)
        else:
            with open(arg_string[1:]) as args_file:
                arg_strings = shlex.split(args_file.read())
                new_arg_strings.extend(arg_strings)
    return new_arg_strings
parser._read_args_from_files = at_read_fn

显然,更清洁的生产版本会在ArgumentParser子类中修改这些方法。

答案 1 :(得分:1)

我认为有更好的答案:使用shlex。

if sys.argv[1].startswith('@'):
    args = parser.parse_args( shlex.split(open(sys.argv[1][1:]).read()) )
else:
    args = parser.parse_args()

这允许您以更自然的方式指定文件中的args,例如,它允许使用空格等号来在一行中指定您的args,如:

arg1
arg2
--opt1 'foo'
--opt2='bar'

shlex.split按照您的预期拆分:

['arg1', 'arg2', '--opt1', 'foo', '--opt2=bar']

这种方法唯一没有的是它希望@ file.txt是第一个参数。

答案 2 :(得分:0)

问题在于,当在文件中指定时,每个参数必须具有' ='它与选项名称之间。虽然argparse在从命令行(其中space或= ok)运行时在该格式上更灵活一些,但是当从文件运行时,它必须具有' ='。

因此,一个有效的参数文件将是:

--option1=foo
--option2=1234

要注意的其他事项,请确保在行尾没有任何额外的空格,或者当argparse读取文件时,该空格将包含在该选项中。

答案 3 :(得分:0)

尝试这种方式

# encoding: utf-8
import imp
import argparse


class LoadConfigAction(argparse._StoreAction):
    NIL = object()

    def __init__(self, option_strings, dest, **kwargs):
        super(self.__class__, self).__init__(option_strings, dest)
        self.help = "Load configuration from file"

    def __call__(self, parser, namespace, values, option_string=None):
        super(LoadConfigAction, self).__call__(parser, namespace, values, option_string)

        config = imp.load_source('config', values)

        for key in (set(map(lambda x: x.dest, parser._actions)) & set(dir(config))):
            setattr(namespace, key, getattr(config, key))

用法

parser.add_argument("-C", "--config", action=LoadConfigAction, default=None)
parser.add_argument("-H", "--host")

示例配置(真实是python文件)

# Config example /etc/my.conf
import os

# Parameter definition
host = os.getenv("HOST", "127.0.0.1")