允许在 Python 命令行中覆盖配置选项的最佳方法是什么?

发布于 2024-09-17 18:23:42 字数 349 浏览 7 评论 0原文

我有一个 Python 应用程序,需要相当多(~30)个配置参数。到目前为止,我使用 OptionParser 类在应用程序本身中定义默认值,并且可以在调用应用程序时在命令行中更改各个参数。

现在我想使用“正确的”配置文件,例如来自 ConfigParser 类的配置文件。同时,用户仍然应该能够在命令行更改各个参数。

我想知道是否有任何方法可以结合这两个步骤,例如使用 optparse (或较新的 argparse )来处理命令行选项,但从 ConfigParse 语法中的配置文件中读取默认值。

有什么想法如何以简单的方式做到这一点?我真的不喜欢手动调用 ConfigParse,然后手动将所有选项的所有默认值设置为适当的值...

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.

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.

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.

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...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

活泼老夫 2024-09-24 18:23:42

我刚刚发现你可以使用 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 for help 稍微复杂一些。

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())

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.:

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 -h for help properly.

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())
瞄了个咪的 2024-09-24 18:23:42

查看 ConfigArgParse - 它是一个新的 PyPI 包(开源),作为 argparse 的替代品,增加了对配置文件和环境变量的支持。

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.

吹泡泡o 2024-09-24 18:23:42

我使用 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')

I'm using ConfigParser and argparse with subcommands to handle such tasks. The important line in the code below is:

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.

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')
软糯酥胸 2024-09-24 18:23:42

我不能说这是最好的方法,但我有一个我制作的 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))

随意浏览来源。测试位于同级目录中。

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...

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.

×眷恋的温暖 2024-09-24 18:23:42

更新:这个答案仍然有问题;例如,它无法处理必需的参数,并且需要笨拙的配置语法。相反, ConfigArgParse 似乎正是这个问题所要求的,并且是一个透明的、直接的替代品。

当前的一个问题是,如果配置文件中的参数无效,则不会出错。这是一个具有不同缺点的版本:您需要在键中包含 --- 前缀。

这是 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)

这是配置文件的示例:

# 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!'

主要缺点是它使用了 parser.parse_args 来进行错误检查来自 ArgumentParser,但我不知道有任何替代方案。

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.

水波映月 2024-09-24 18:23:42

您可以使用 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.

您可以组合来自命令行的值,环境变量、配置文件,如果该值不存在,则定义一个默认值。

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'

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'

fromfile_prefix_chars

也许不是最干净的 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())

然后:

$ 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')

文档: https://docs.python .org/3.6/library/argparse.html#fromfile-prefix-chars

这个@opts.txt约定有一些先例,例如在GCC工具链中:命令行中的“@”是什么意思?

如何使用正确的 CLI 选项来指示选项文件而不是丑陋的 @ 东西:如何让 argparse 从带有选项而不是前缀的文件中读取参数

在 Python 3.6.5、Ubuntu 18.04 上测试。

fromfile_prefix_chars

Maybe not the cleanest of APIs, 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

This @opts.txt convention has some precedents e.g. in the GCC toolchain: What does "@" at the command line mean?

How to use a proper CLI option to indicate the options file rather than the ugly @ thing: how to get argparse to read arguments from a file with an option rather than prefix

Tested on Python 3.6.5, Ubuntu 18.04.

爱给你人给你 2024-09-24 18:23:42

这里值得一提的是 jsonargparse (注意我是该库的作者),具有 MIT 许可证并可在 PyPI 上使用。它也是 argparse 的扩展,支持从配置文件和环境变量加载。它与 ConfigArgParse 类似,但它更新,具有更多有用的功能并且维护得很好。

一个示例 main.py 如下:

from jsonargparse import ArgumentParser, ActionConfigFile

parser = ArgumentParser()
parser.add_argument("--config", action=ActionConfigFile)
parser.add_argument("--opt1", default="default 1")
parser.add_argument("--opt2", default="default 2")
args = parser.parse_args()
print(args.opt1, args.opt2)

拥有一个配置文件 config.yaml ,其内容为:

opt1: one
opt2: two

然后从命令行运行一个示例:

$ python main.py --config config.yaml --opt1 ONE
ONE two

Worth mentioning here is jsonargparse (Note I am an author of the library), with MIT license and available on PyPI. It is also an extension of argparse that supports loading from config files and environment variables. It is similar to ConfigArgParse, but it is newer, with many more useful features and well maintained.

An example main.py would be:

from jsonargparse import ArgumentParser, ActionConfigFile

parser = ArgumentParser()
parser.add_argument("--config", action=ActionConfigFile)
parser.add_argument("--opt1", default="default 1")
parser.add_argument("--opt2", default="default 2")
args = parser.parse_args()
print(args.opt1, args.opt2)

Having a config file config.yaml with content:

opt1: one
opt2: two

Then an example run from the command line:

$ python main.py --config config.yaml --opt1 ONE
ONE two
临走之时 2024-09-24 18:23:42

尝试以这种方式

# 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")

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")
不回头走下去 2024-09-24 18:23:42

parse_args() 可以获取现有的命名空间并将现有的命名空间与当前正在解析的参数/选项合并; “当前解析”中的选项 args/options 优先覆盖现有命名空间中的任何内容:

foo_parser = argparse.ArgumentParser()
foo_parser.add_argument('--foo')

ConfigNamespace = argparse.Namespace()
setattr(ConfigNamespace, 'foo', 'foo')

args = foo_parser.parse_args([], namespace=ConfigNamespace)
print(args)
# Namespace(foo='foo')

# value `bar` will override value `foo` from ConfigNamespace
args = foo_parser.parse_args(['--foo', 'bar'], namespace=ConfigNamespace)
print(args)
# Namespace(foo='bar')

我已经将其模拟为真正的配置文件选项。我解析两次,一次作为“预解析”以查看用户是否传递了配置文件,然后再次进行集成可选配置文件命名空间的“最终解析”。

我有一个非常简单的 JSON 配置文件 config.ini:

[DEFAULT]
delimiter = |

当我

import argparse
import configparser

parser = argparse.ArgumentParser()
parser.add_argument('-c', '--config-file', type=str)
parser.add_argument('-d', '--delimiter', type=str, default=',')

# Parse cmd-line args to see if config-file is specified
pre_args = parser.parse_args()

# Even if config is not specified, need empty Namespace to pass to final `parse_args()`
ConfigNamespace = argparse.Namespace()

if pre_args.config_file:
    config = configparser.ConfigParser()
    config.read(pre_args.config_file)

    for name, val in config['DEFAULT'].items():
        setattr(ConfigNamespace, name, val)


# Parse cmd-line args again, merging with ConfigNamespace, 
# cmd-line args take precedence
args = parser.parse_args(namespace=ConfigNamespace)

print(args)

使用各种 cmd 行设置运行此命令时,我得到:

./main.py
Namespace(config_file=None, delimiter=',')

./main.py -c config.ini
Namespace(config_file='config.ini', delimiter='|')

./main.py -c config.ini -d \;
Namespace(config_file='config.ini', delimiter=';')

parse_args() can take an existing Namespace and merge the existing Namespace with args/options it's currently parsing; the options args/options in the "current parsing" take precedence an override anything in the existing Namespace:

foo_parser = argparse.ArgumentParser()
foo_parser.add_argument('--foo')

ConfigNamespace = argparse.Namespace()
setattr(ConfigNamespace, 'foo', 'foo')

args = foo_parser.parse_args([], namespace=ConfigNamespace)
print(args)
# Namespace(foo='foo')

# value `bar` will override value `foo` from ConfigNamespace
args = foo_parser.parse_args(['--foo', 'bar'], namespace=ConfigNamespace)
print(args)
# Namespace(foo='bar')

I've mocked it up for a real config file option. I'm parsing twice, once, as a "pre-parse" to see if the user passed a config-file, and then again for the "final parse" that integrates the optional config-file Namespace.

I have this very simple JSON config file, config.ini:

[DEFAULT]
delimiter = |

and when I run this:

import argparse
import configparser

parser = argparse.ArgumentParser()
parser.add_argument('-c', '--config-file', type=str)
parser.add_argument('-d', '--delimiter', type=str, default=',')

# Parse cmd-line args to see if config-file is specified
pre_args = parser.parse_args()

# Even if config is not specified, need empty Namespace to pass to final `parse_args()`
ConfigNamespace = argparse.Namespace()

if pre_args.config_file:
    config = configparser.ConfigParser()
    config.read(pre_args.config_file)

    for name, val in config['DEFAULT'].items():
        setattr(ConfigNamespace, name, val)


# Parse cmd-line args again, merging with ConfigNamespace, 
# cmd-line args take precedence
args = parser.parse_args(namespace=ConfigNamespace)

print(args)

with various cmd-line settings, I get:

./main.py
Namespace(config_file=None, delimiter=',')

./main.py -c config.ini
Namespace(config_file='config.ini', delimiter='|')

./main.py -c config.ini -d \;
Namespace(config_file='config.ini', delimiter=';')
聽兲甴掵 2024-09-24 18:23:42

我发现现有的答案不足,尤其是在处理子命令和所需参数时。

这是我最终得到的解决方案:

# Create a separate parser for the --config-file argument
config_parser = argparse.ArgumentParser(
    add_help=False
)
config_parser.add_argument(
    "--config-file",
    metavar="FILE"
)

# Parse the config file arg, but hold on to all the other args
# (we'll need them later, for the main parser)
args, remaining_argv = config_parser.parse_known_args()
config_data = {}
if args.config_file:
    # config_data = <Process your file however you'd like>

# Use the config to generate new CLI arguments
argv = remaining_argv
for k, v in config_data.items():
    argv.append(f"--{k}")
    argv.append(str(v))

# Setup your main parser
parser = argparse.ArgumentParser(
    # ...
)
# Parse arguments
args = parser.parse_args(argv)

也许有点时髦,但它非常适合我的用例。

I've found the existing answers to come up short, especially when dealing with subcommands and required arguments.

Here's the solution I ended up with:

# Create a separate parser for the --config-file argument
config_parser = argparse.ArgumentParser(
    add_help=False
)
config_parser.add_argument(
    "--config-file",
    metavar="FILE"
)

# Parse the config file arg, but hold on to all the other args
# (we'll need them later, for the main parser)
args, remaining_argv = config_parser.parse_known_args()
config_data = {}
if args.config_file:
    # config_data = <Process your file however you'd like>

# Use the config to generate new CLI arguments
argv = remaining_argv
for k, v in config_data.items():
    argv.append(f"--{k}")
    argv.append(str(v))

# Setup your main parser
parser = argparse.ArgumentParser(
    # ...
)
# Parse arguments
args = parser.parse_args(argv)

Maybe a little funky, but it works well for my use case.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文