Python의 명령 줄에서 구성 옵션을 재정의하는 가장 좋은 방법은 무엇입니까?
꽤 많은 (~ 30) 구성 매개 변수가 필요한 Python 애플리케이션이 있습니다. 지금까지는 OptionParser 클래스를 사용하여 앱 자체에서 기본값을 정의했으며 애플리케이션을 호출 할 때 명령 줄에서 개별 매개 변수를 변경할 수있었습니다.
이제 ConfigParser 클래스의 '적절한'구성 파일을 사용하고 싶습니다. 동시에 사용자는 명령 줄에서 개별 매개 변수를 변경할 수 있어야합니다.
두 단계를 결합하는 방법이 있는지 궁금합니다. 예를 들어 optparse (또는 최신 argparse)를 사용하여 명령 줄 옵션을 처리하지만 ConfigParse 구문의 구성 파일에서 기본값을 읽습니다.
쉬운 방법으로 수행하는 방법에 대한 아이디어가 있습니까? ConfigParse를 수동으로 호출 한 다음 모든 optinos의 모든 기본값을 적절한 값으로 수동으로 설정하는 것을 정말 좋아하지 않습니다.
방금 argparse.ArgumentParser.parse_known_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())
ConfigArgParse를 확인하세요 . 구성 파일 및 환경 변수에 대한 지원이 추가 된 argparse를 대체 하는 새로운 PyPI 패키지 ( 오픈 소스 )입니다.
이러한 작업을 처리하기 위해 하위 명령과 함께 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')
이것이 최선의 방법이라고 말할 수는 없지만 내가 만든 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))
소스를 자유롭게 찾아보십시오 . 테스트는 형제 디렉터리에 있습니다.
이런 식으로 시도
# 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")
Update: This answer still has issues; for example, it cannot handle required
arguments, and requires an awkward config syntax. Instead, ConfigArgParse seems to be exactly what this question asks for, and is a transparent, drop-in replacement.
One issue with the current is 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.
Here's the python code (Gist link with MIT license):
# 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_args
somewhat hackily in order to obtain the error checking from ArgumentParser, but I am not aware of any alternatives to this.
fromfile_prefix_chars
Maybe not the perfect API, but worth knowing about. 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())
Then:
$ 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')
Documentation: https://docs.python.org/3.6/library/argparse.html#fromfile-prefix-chars
Tested on Python 3.6.5, Ubuntu 18.04.
You can use 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'
'code' 카테고리의 다른 글
MSBuild 파일에 대한 표준 파일 확장자가 있습니까? (0) | 2020.11.15 |
---|---|
Rabbitmq 또는 Gearman-작업 대기열 선택 (0) | 2020.11.15 |
iPhone / iPad 앱 코드 난독 화-가능합니까? (0) | 2020.11.15 |
어떤 cpan 설치 프로그램이 적합한가요? (0) | 2020.11.15 |
4xx / 5xx에서 예외를 throw하지 않고 Powershell 웹 요청 (0) | 2020.11.15 |