允许在 Python 的命令行中覆盖配置选项的最佳方法是什么?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3609852/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Which is the best way to allow configuration options be overridden at the command line in Python?
提问by andreas-h
I have a Python application which needs quite a few (~30) configuration parameters. Up to now, I used the OptionParser class to define default values in the app itself, with the possibility to change individual parameters at the command line when invoking the application.
我有一个 Python 应用程序,它需要很多(~30)个配置参数。到目前为止,我使用 OptionParser 类在应用程序本身中定义默认值,并可以在调用应用程序时在命令行中更改各个参数。
Now I would like to use 'proper' configuration files, for example from the ConfigParser class. At the same time, users should still be able to change individual parameters at the command line.
现在我想使用“适当的”配置文件,例如来自 ConfigParser 类。同时,用户应该仍然能够在命令行中更改单个参数。
I was wondering if there is any way to combine the two steps, e.g. use optparse (or the newer argparse) to handle command line options, but reading the default values from a config file in ConfigParse syntax.
我想知道是否有任何方法可以将这两个步骤结合起来,例如使用 optparse(或较新的 argparse)来处理命令行选项,但从 ConfigParse 语法中的配置文件中读取默认值。
Any ideas how to do this in an easy way? I don't really fancy manually invoking ConfigParse, and then manually setting all defaults of all the options to the appropriate values...
任何想法如何以简单的方式做到这一点?我真的不喜欢手动调用 ConfigParse,然后手动将所有选项的所有默认值设置为适当的值......
采纳答案by Von
I just discovered you can do this with argparse.ArgumentParser.parse_known_args(). Start by using parse_known_args()to parse a configuration file from the commandline, then read it with ConfigParser and set the defaults, and then parse the rest of the options with parse_args(). This will allow you to have a default value, override that with a configuration file and then override that with a commandline option. E.g.:
我刚刚发现你可以用argparse.ArgumentParser.parse_known_args(). 首先使用parse_known_args()从命令行解析配置文件,然后使用 ConfigParser 读取它并设置默认值,然后使用parse_args(). 这将允许您拥有一个默认值,使用配置文件覆盖它,然后使用命令行选项覆盖它。例如:
Default with no user input:
没有用户输入的默认值:
$ ./argparse-partial.py
Option is "default"
Default from configuration file:
配置文件中的默认值:
$ cat argparse-partial.config
[Defaults]
option=Hello world!
$ ./argparse-partial.py -c argparse-partial.config
Option is "Hello world!"
Default from configuration file, overridden by commandline:
配置文件中的默认值,由命令行覆盖:
$ ./argparse-partial.py -c argparse-partial.config --option override
Option is "override"
argprase-partial.py follows. It is slightly complicated to handle -hfor help properly.
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())
回答by Blair Conrad
I can't say it's the best way, but I have an OptionParser class that I made that does just that - acts like optparse.OptionParser with defaults coming from a config file section. You can have it...
我不能说这是最好的方法,但我有一个我制作的 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))
Feel free to browse the source. Tests are in a sibling directory.
随意浏览源代码。测试位于同级目录中。
回答by xubuntix
I'm using ConfigParser and argparse with subcommands to handle such tasks. The important line in the code below is:
我将 ConfigParser 和 argparse 与子命令一起使用来处理此类任务。下面代码中的重要一行是:
subp.set_defaults(**dict(conffile.items(subn)))
This will set the defaults of the subcommand (from argparse) to the values in the section of the config file.
这会将子命令(来自 argparse)的默认值设置为配置文件部分中的值。
A more complete example is below:
一个更完整的例子如下:
####### 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')
回答by user553965
Check out ConfigArgParse- its a new PyPI package (open source) that serves as a drop in replacement for argparse with added support for config files and environment variables.
查看ConfigArgParse- 它是一个新的 PyPI 包(开源),它作为 argparse 的替代品,增加了对配置文件和环境变量的支持。
回答by mosquito
Try to this way
试试这种方式
# 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))
Use it:
用它:
parser.add_argument("-C", "--config", action=LoadConfigAction)
parser.add_argument("-H", "--host", dest="host")
And create example config:
并创建示例配置:
# Example config: /etc/myservice.conf
import os
host = os.getenv("HOST_NAME", "localhost")
回答by Achal Dave
Update: This answer still has issues; for example, it cannot handle requiredarguments, and requires an awkward config syntax. Instead, ConfigArgParseseems to be exactly what this question asks for, and is a transparent, drop-in replacement.
更新:这个答案仍然有问题;例如,它无法处理required参数,并且需要笨拙的配置语法。相反,ConfigArgParse似乎正是这个问题所要求的,并且是一个透明的、直接的替代品。
One issue with the currentis that it will not error if the arguments in the config file are invalid. Here's a version with a different downside: you'll need to include the --or -prefix in the keys.
当前的一个问题是,如果配置文件中的参数无效,它不会出错。这是一个具有不同缺点的版本:您需要在键中包含--or-前缀。
Here's the python code (Gist linkwith MIT license):
这是 python 代码(带有 MIT 许可证的Gist 链接):
# Filename: main.py
import argparse
import configparser
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('--config_file', help='config file')
args, left_argv = parser.parse_known_args()
if args.config_file:
with open(args.config_file, 'r') as f:
config = configparser.SafeConfigParser()
config.read([args.config_file])
parser.add_argument('--arg1', help='argument 1')
parser.add_argument('--arg2', type=int, help='argument 2')
for k, v in config.items("Defaults"):
parser.parse_args([str(k), str(v)], args)
parser.parse_args(left_argv, args)
print(args)
Here's an example of a config file:
下面是一个配置文件的例子:
# Filename: config_correct.conf
[Defaults]
--arg1=Hello!
--arg2=3
Now, running
现在,运行
> python main.py --config_file config_correct.conf --arg1 override
Namespace(arg1='override', arg2=3, config_file='test_argparse.conf')
However, if our config file has an error:
但是,如果我们的配置文件有错误:
# config_invalid.conf
--arg1=Hello!
--arg2='not an integer!'
Running the script will produce an error, as desired:
根据需要,运行脚本将产生错误:
> 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!'
The main downside is that this uses parser.parse_argssomewhat hackily in order to obtain the error checking from ArgumentParser, but I am not aware of any alternatives to this.
主要的缺点是,parser.parse_args为了从 ArgumentParser 获取错误检查,这有点笨拙地使用,但我不知道有任何替代方法。
回答by Vlad Bezden
You can use ChainMap
您可以使用ChainMap
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.
You can combine values from command line, environment variables, configuration file, and in case if the value is not there define a default value.
您可以组合来自命令行、环境变量、配置文件的值,如果该值不存在,则定义默认值。
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'

