从类中加载配置,使其行为类似于属性

Posted

技术标签:

【中文标题】从类中加载配置,使其行为类似于属性【英文标题】:Loading config from class in such a way that it behaves like a property 【发布时间】:2018-06-29 06:53:37 【问题描述】:

我正在尝试为 python 创建一个 yaml 驱动的配置模块,该模块既可以从平面 yaml 文件加载,也可以选择从 sys.argv 加载参数。它还将加载的属性保存到一个局部变量中(这很丑陋,但每次引用 config 时解析 yaml 文件仍然更可取)。

我有这个工作,但我必须导入它的方式似乎过头了,而且不是很pythonic。有没有更优雅的方式来构造它,以便:

我可以使用单个导入语句 我可以确保配置的加载次数(在使用它的不同模块中)不会超过绝对必要的次数

结构:

/app
    /__init__.py
    /__main__.py # calls __init__.py
    /config.yaml
    /applib
        /__init__.py
        /__config_loader.py

在应用程序的主 __init__.py 中,我像这样加载配置:

from applib.config_loader import Config; config = Config()
#                                          ^ ew!

config_loader.py,我有:

import argparse
import yaml

class Config:
    _config = None

    @property
    def config(self):
        if self._config is not None:
            print('Using what we already loaded')
        return self._config or Config()

    def __init__(self):
        if self._config is None:
            print('Loading config')

            with open(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'config.yaml')) as yaml_config_file:
                self._config = yaml.load(yaml_config_file)

            parser = argparse.ArgumentParser(description = 'MyApp')
            parser.add_argument('--an_arg')
            args = parser.parse_args()

    def __getattr__(self, name):
        return self._config[name] or None

当然不是让它工作的最漂亮的方式,而且似乎我无法理解在 python 中做事的新旧方式使情况过于复杂。

我希望能够像这样加载它:

from applib.config_loader import config

print(config['an_arg'])

【问题讨论】:

"但是每次引用config时还是解析yaml文件更好"为什么每次引用config都要解析yaml文件? 从我的测试来看,每次引用config时都会调用__init__,因此每次都会解析yaml文件。也许我错了? 为什么不在 applib.config 文件的末尾添加行 config = Config() 呢?然后from applib.config import config 将按照您的意愿工作。 顺便说一句,变量Config._config 没有被构造函数Config.__init__ 中的语句self._config = [something] 初始化。这将为该实例创建一个名为“_config”的新 member 变量,但不会影响名为“_config”的 class 变量。除非您执行语句 Config._config = [something],否则不会设置类变量。 是的,@PaulCornelius 你完全正确。至少我想多了是对的???把它扔进去,我会接受的。 【参考方案1】:

解决你的问题的简单方法是把这行:

config = Config()

在类Config的定义之后。但是还有一个更简单的解决方案:config_loader.py 中大约一半的代码可以被删除。像这样:

import argparse
import yaml

class _Config:
    def __init__(self):
        print('Loading config')
        with open(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'config.yaml')) as yaml_config_file:
            self.config = yaml.load(yaml_config_file)
        parser = argparse.ArgumentParser(description = 'MyApp')
        parser.add_argument('--an_arg')
        self.args = parser.parse_args()

    def __getattr__(self, name):
        try:
            return self.config[name]
        except KeyError:
            return getattr(self.args, name)

config = _Config()

您的客户端代码现在可以:

from config_loader import config

您以通常的方式 config.xxx 访问 config 中的属性。您不需要弄乱单例对象行为或类变量。 _Config 前面的下划线保证类构造函数只会在模块加载时被调用一次。只能通过名为config 的变量访问该类。

我还修复了第二个未讨论的问题:您没有在 Config 构造函数中使用变量“args”,因此实际上并没有对命令行解析器的输出做任何事情。我把 args 做成了一个成员变量,并适当地修改了__getattr__ 函数。

【讨论】:

太棒了!很棒的收获,我在实施后也发现了自己。

以上是关于从类中加载配置,使其行为类似于属性的主要内容,如果未能解决你的问题,请参考以下文章

Java中加载properties配置文件的几种方式

在Cassandra中加载cassandra.yaml之外的其他配置文件

Spring中获取Bean的几种方式

JavaSpring

无法在 Spring Boot 中加载外部属性

访问 UIImage 属性而不在内存中加载图像