我正在尝试在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没有看到它。
答案 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")