如何为包含许多不同 类 的 Python 程序编写配置文件?

How to write configuration files for a Python program which contains lots of different classes?

目前我有一个 Python 文件,其中包含许多 classes,每个 class 在其构造函数中采用 5-10 个参数。

每次我想修改那些参数的默认值时,我需要遍历整个文件来寻找那些构造函数,然后手动修改它们,这不是太复杂,但会有点有时有点乱。

我想知道有没有办法为它编写一个人类可读的配置文件,以便我可以直接修改这个配置文件中的一些条目来更改相应的默认参数?我应该怎么做?

谢谢!

您可以在文件的开头定义一些常量,以便将所有默认值集中在一个地方:

A_PARAM1_DEFAULT = 42
class A:
    def __init__(param1=A_PARAM1_DEFAULT):
        # do something

我的建议应该被视为一种意见而不是知识(这让我质疑这个 post 对 SO 有多合适)。

如果您已经有一个应用程序,最简单也是我首选的方法是将配置文件以 KEY=VALUE 格式的纯文本形式存储。在我看来,这是最简单、最干净的方法。纯文本在任何地方都是可以理解的(不是一些 qwerky 格式)并且 KEY=VALUE 非常容易单独阅读和理解。

举个例子:

port=443
host=localhost
proxy=127.0.0.1

您可以使用特定名称将其保存在当前工作目录中(.NAME 格式以使其隐藏在某些 OS 上)

此时,解析内容相当简单,具体取决于您的应用程序的结构。(1)

with open('filename') as config_file:
    config_entries = config_file.readlines()
    for line in config_entries:
        pair = line.split('=')
        key = pair[0]
        val = pair[1]

从本质上讲,大部分元数据都与您的其余代码分离,以便将来维护和更新时保持这种状态。

当然,这是一种简单的方法,您可以选择(如@Mark 所建议的那样)使用 configparser,但我认为它增加了额外的复杂性,在大多数情况下您可以轻松避免。


(1) 没有测试下面的代码。

您可以编写任何格式的配置文件。

Python 支持 JSON, XML and a format similar to traditional sectioned config files (configparser)。所有这些格式都支持嵌套结构(最后一种不太有用的方式)。

许多 Python 项目选择的另一个选项是在 Python 本身中编写配置文件。事实上,一本 Python 字典几乎看起来像 JSON,所以即使对 Python 不是很了解的人也应该很容易阅读和书写。然后可以加载该文件并对其进行动态评估,这意味着您还可以使用奇特的东西,例如检索环境变量、递归加载其他文件或使用方便的函数,例如 os.path.whatever。如果您不想执行此类“不受信任”的代码,请查看 ast.literal_eval.

无论您选择什么格式,您都应该编写一个小 config 模块,该模块提供一个函数来检索给定键的配置值。键可以是像 module.submodule.class.parameter 这样的字符串,它被函数分成几部分,用于在配置文件的层次结构中查找条目。最好将配置一次加载到内存中的数据结构中,然后为来自该结构的所有请求提供服务。您可以在第一次调用函数时延迟加载。

关于在 Python 中编写配置 classes 的最佳方式已经有很多讨论。我更喜欢使用基于 classutilities 包 (pip install classutilities) 的 classes,请参阅 https://pypi.org/project/classutilities/.

上的文档

此包强制您遵循 PEP8(常量 class 变量的大写名称,无 class 实例和方法等)并强制 class 表现为“ static" class(在从 Java/C# 得知的逻辑中)。

本地和生产堆栈的配置示例:

# File base_config.py
from classutilities import ConfigClassMixin, classproperty


class ConfigBase(ConfigClassMixin):
    """Base configuration options for all stacks"""
    PGSQL_HOST: str
    PGSQL_USER: str
    PGSQL_PASS: str
    PGSQL_PORT: int
    PGSQL_DATABASE: str

    CORS_ORIGINS: list[str]

    @classproperty
    def DB_CONNECTION(cls):
        """Create database connection."""
        return ...

本地配置文件可以如下所示:

# File config_local.py
from .config_base import ConfigBase


class ConfigLocal(ConfigBase):
    """Local stack configuration"""
    PGSQL_HOST: str = "database"
    PGSQL_USER: str = "whoever"
    PGSQL_PASS: str = "..."
    PGSQL_PORT: int = 5432
    PGSQL_DATABASE: str = "whatever"

    CORS_ORIGINS: list[str] = ["*"]

生产堆栈可能会使用环境变量初始化大多数变量(但从技术上讲,它是相同的)。

然后你可以很容易地select基于环境变量的堆栈配置(定义你想要的堆栈)在__init__.py文件中(考虑到整个配置构造在配置子-包裹)。例如:

# File __init__.py
import os

from .config_base import ConfigBase
from .config_local import ConfigLocal
from .config_production import ConfigProduction

# Get the stack definition
stack: str = os.getenv("ENVIRONMENT", "local")

# Select correct configuration
CONFIG: type[ConfigBase] = ConfigLocal

if stack == "local":
    CONFIG: type[ConfigBase] = ConfigLocal
elif stack == "production":
    CONFIG: type[ConfigBase] = ConfigProduction
# other options here (typically 'production')
else:
    raise RuntimeError("Wrong stack name")

然后您可以通过从子包中导入变量 CONFIG 来使用您的配置。例如:

from whatever.config import CONFIG

# ...
# To access configuration variables/properties:
CONFIG.DB_CONNECTION.query(...)

我认为这是处理配置问题的最干净的方法。