在Python中,允许在命令行重写配置选项的最佳方法是什么?
我有一个Python应用程序,它需要很多(~30)个配置参数。到目前为止,我使用OptionParser类在应用程序本身中定义默认值,在调用应用程序时可以在命令行中更改各个参数 现在我想使用“正确”的配置文件,例如来自ConfigParser类的配置文件。同时,用户应该仍然能够在命令行中更改各个参数 我想知道是否有任何方法可以将这两个步骤结合起来,例如使用optparse(或更新的argparse)来处理命令行选项,但使用ConfigParse语法从配置文件中读取默认值在Python中,允许在命令行重写配置选项的最佳方法是什么?,python,command-line,configuration-files,Python,Command Line,Configuration Files,我有一个Python应用程序,它需要很多(~30)个配置参数。到目前为止,我使用OptionParser类在应用程序本身中定义默认值,在调用应用程序时可以在命令行中更改各个参数 现在我想使用“正确”的配置文件,例如来自ConfigParser类的配置文件。同时,用户应该仍然能够在命令行中更改各个参数 我想知道是否有任何方法可以将这两个步骤结合起来,例如使用optparse(或更新的argparse)来处理命令行选项,但使用ConfigParse语法从配置文件中读取默认值 有没有办法简单地做到这一
有没有办法简单地做到这一点?我真的不喜欢手动调用ConfigParse,然后手动将所有选项的所有默认值设置为适当的值…我不能说这是最好的方法,但我有一个OptionParser类,我制作了一个OptionParser类,它可以做到这一点,就像optparse.OptionParser一样,默认值来自配置文件部分。你可以拥有它
class OptionParser(optparse.OptionParser):
def __init__(self, **kwargs):
import sys
import os
config_file = kwargs.pop('config_file',
os.path.splitext(os.path.basename(sys.argv[0]))[0] + '.config')
self.config_section = kwargs.pop('config_section', 'OPTIONS')
self.configParser = ConfigParser()
self.configParser.read(config_file)
optparse.OptionParser.__init__(self, **kwargs)
def add_option(self, *args, **kwargs):
option = optparse.OptionParser.add_option(self, *args, **kwargs)
name = option.get_opt_string()
if name.startswith('--'):
name = name[2:]
if self.configParser.has_option(self.config_section, name):
self.set_default(name, self.configParser.get(self.config_section, name))
。测试在同级目录中。我使用ConfigParser和argparse以及子命令来处理此类任务。下面代码中的重要行是:
subp.set_defaults(**dict(conffile.items(subn)))
这将子命令(从argparse)的默认值设置为配置文件部分中的值
下面是一个更完整的示例:
####### content of example.cfg:
# [sub1]
# verbosity=10
# gggg=3.5
# [sub2]
# host=localhost
import ConfigParser
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
parser_sub1 = subparsers.add_parser('sub1')
parser_sub1.add_argument('-V','--verbosity', type=int, dest='verbosity')
parser_sub1.add_argument('-G', type=float, dest='gggg')
parser_sub2 = subparsers.add_parser('sub2')
parser_sub2.add_argument('-H','--host', dest='host')
conffile = ConfigParser.SafeConfigParser()
conffile.read('example.cfg')
for subp, subn in ((parser_sub1, "sub1"), (parser_sub2, "sub2")):
subp.set_defaults(**dict(conffile.items(subn)))
print parser.parse_args(['sub1',])
# Namespace(gggg=3.5, verbosity=10)
print parser.parse_args(['sub1', '-V', '20'])
# Namespace(gggg=3.5, verbosity=20)
print parser.parse_args(['sub1', '-V', '20', '-G','42'])
# Namespace(gggg=42.0, verbosity=20)
print parser.parse_args(['sub2', '-H', 'www.example.com'])
# Namespace(host='www.example.com')
print parser.parse_args(['sub2',])
# Namespace(host='localhost')
我刚刚发现,您可以使用
argparse.ArgumentParser.parse\u known\u args()
实现这一点。首先使用parse_-known_-args()
从命令行解析配置文件,然后使用ConfigParser读取并设置默认值,然后使用parse_-args()
解析其余选项。这将允许您使用默认值,使用配置文件覆盖该值,然后使用命令行选项覆盖该值。例如:
没有用户输入的默认值:
$ ./argparse-partial.py
Option is "default"
配置文件中的默认值:
$ cat argparse-partial.config
[Defaults]
option=Hello world!
$ ./argparse-partial.py -c argparse-partial.config
Option is "Hello world!"
配置文件中的默认值,由命令行覆盖:
$ ./argparse-partial.py -c argparse-partial.config --option override
Option is "override"
argprase-partial.py如下。正确处理-h
以获得帮助有点复杂
import argparse
import ConfigParser
import sys
def main(argv=None):
# Do argv default this way, as doing it in the functional
# declaration sets it at compile time.
if argv is None:
argv = sys.argv
# Parse any conf_file specification
# We make this parser with add_help=False so that
# it doesn't parse -h and print help.
conf_parser = argparse.ArgumentParser(
description=__doc__, # printed with -h/--help
# Don't mess with format of description
formatter_class=argparse.RawDescriptionHelpFormatter,
# Turn off help, so we print all options in response to -h
add_help=False
)
conf_parser.add_argument("-c", "--conf_file",
help="Specify config file", metavar="FILE")
args, remaining_argv = conf_parser.parse_known_args()
defaults = { "option":"default" }
if args.conf_file:
config = ConfigParser.SafeConfigParser()
config.read([args.conf_file])
defaults.update(dict(config.items("Defaults")))
# Parse rest of arguments
# Don't suppress add_help here so it will handle -h
parser = argparse.ArgumentParser(
# Inherit options from config_parser
parents=[conf_parser]
)
parser.set_defaults(**defaults)
parser.add_argument("--option")
args = parser.parse_args(remaining_argv)
print "Option is \"{}\"".format(args.option)
return(0)
if __name__ == "__main__":
sys.exit(main())
签出-它是一个新的PyPI包(),作为argparse的替代品,添加了对配置文件和环境变量的支持。尝试这种方法
# 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)
parser.add_argument("-H", "--host", dest="host")
并创建示例配置:
# Example config: /etc/myservice.conf
import os
host = os.getenv("HOST_NAME", "localhost")
更新:这个答案仍然有问题;例如,它不能处理
required
参数,并且需要笨拙的配置语法。相反,这似乎正是这个问题所要求的,而且是一个透明的替代品
的一个问题是,如果配置文件中的参数无效,则不会出错。这是一个具有不同缺点的版本:您需要在键中包含--
或-
前缀
以下是python代码(使用MIT许可证):
下面是一个配置文件的示例:
# Filename: config_correct.conf
[Defaults]
--arg1=Hello!
--arg2=3
现在,跑步
> python main.py --config_file config_correct.conf --arg1 override
Namespace(arg1='override', arg2=3, config_file='test_argparse.conf')
但是,如果配置文件有错误:
# config_invalid.conf
--arg1=Hello!
--arg2='not an integer!'
根据需要,运行脚本将产生错误:
> python main.py --config_file config_invalid.conf --arg1 override
usage: test_argparse_conf.py [-h] [--config_file CONFIG_FILE] [--arg1 ARG1]
[--arg2 ARG2]
main.py: error: argument --arg2: invalid int value: 'not an integer!'
主要的缺点是,为了从ArgumentParser获得错误检查,这使用了
parser.parse_args
,但我不知道有任何替代方法
也许不是完美的API,但值得了解
main.py
#!/usr/bin/env python3
import argparse
parser = argparse.ArgumentParser(fromfile_prefix_chars='@')
parser.add_argument('-a', default=13)
parser.add_argument('-b', default=42)
print(parser.parse_args())
然后:
文件:
在Python 3.6.5和Ubuntu 18.04上测试。您可以使用
您可以组合来自命令行、环境变量、配置文件的值,如果该值不存在,则定义默认值
import os
from collections import ChainMap, defaultdict
options = ChainMap(command_line_options, os.environ, config_file_options,
defaultdict(lambda: 'default-value'))
value = options['optname']
value2 = options['other-option']
print(value, value2)
'optvalue', 'default-value'
我的问题是argparse设置配置文件路径,而配置文件设置argparse默认值。。。愚蠢的鸡蛋问题上面有人问我重复使用上面的代码,我现在把它放到公共域中。“公共域”让我发笑。我只是个傻孩子。啊!这是一段非常酷的代码,但是命令行覆盖的属性的SafeConfigParser插值不起作用。例如,如果您将以下行添加到argparse-partial.config
other=%(option)s您是残忍的
则other
将始终解析为Hello world您是残忍的
,即使选项
在命令行中被重写为其他内容。。argghh解析器!请注意,set_defaults仅在参数名称不包含破折号或下划线时有效。所以我们可以选择--myVar而不是--myVar(不幸的是,这很难看)。要为配置文件启用区分大小写的功能,请在解析文件之前使用config.optionxform=str,这样myVar就不会转换为myVar。请注意,如果要向应用程序添加--version
选项,最好将其添加到conf_parser
而不是parser
并在打印帮助后退出应用程序。如果将--version
添加到解析器
中,并使用--version
标志启动应用程序,则应用程序将不必要地尝试打开并解析args.conf_文件
配置文件(该文件可能格式不正确,甚至不存在,从而导致异常))谢谢你指出这一点。谢谢-看起来不错!该网页还将ConfigArgParse与其他选项进行比较,包括argparse、ConfArgParse、appsettings、argparse_cnfig、yconf、hieropt和configuratiUpdate:该软件包是argparse的替代品,允许通过配置文件和/或环境变量设置选项。请参见@user553965相关的下面的答案:与按照所需优先顺序更新的dict
链相比,链表有什么优势?使用defaultdict
可能有一个优势,可以设置新颖或不受支持的选项,但这与ChainMap
不同。我想我错过了什么。
$ printf -- '-a\n1\n-b\n2\n' > opts.txt
$ ./main.py
Namespace(a=13, b=42)
$ ./main.py @opts.txt
Namespace(a='1', b='2')
$ ./main.py @opts.txt -a 3 -b 4
Namespace(a='3', b='4')
$ ./main.py -a 3 -b 4 @opts.txt
Namespace(a='1', b='2')
A ChainMap groups multiple dicts or other mappings together to create a single, updateable view. If no maps are specified, a single empty dictionary is provided so that a new chain always has at least one mapping.
import os
from collections import ChainMap, defaultdict
options = ChainMap(command_line_options, os.environ, config_file_options,
defaultdict(lambda: 'default-value'))
value = options['optname']
value2 = options['other-option']
print(value, value2)
'optvalue', 'default-value'