Python 配置管理
配置与代码分离:环境变量、文件与密钥边界;校验失败要早且明确。
#concept
#growing
#dev
#python
#python
[!info] related notes
Python 配置管理
这个概念为什么出现
把数据库密码写进代码仓库是事故。配置应:
- 按环境变化
- 可校验
- 密钥不进 git
[!abstract] 一句话理解 从环境/文件加载配置并校验类型与必填项;代码只读配置对象,密钥走密钥管理。
最小可运行示例
先把示例放进业务场景,再看代码:
场景:服务启动读取超时与数据库 URL
# config_from_env.py
# 业务意图:从环境变量构建配置,缺省与校验。
# 教学点:
# - os.environ;
# - 默认值;
# - 启动期失败。
import math
import os
from collections.abc import Mapping
from dataclasses import dataclass, field
@dataclass(frozen=True)
class AppConfig:
database_url: str = field(repr=False)
request_timeout_s: float
def load_config(env: Mapping[str, str] | None = None) -> AppConfig:
e = env if env is not None else os.environ
url = e.get("DATABASE_URL", "").strip()
if not url:
raise RuntimeError("DATABASE_URL is required")
try:
timeout = float(e.get("REQUEST_TIMEOUT_S", "3"))
except ValueError:
raise RuntimeError("REQUEST_TIMEOUT_S must be a finite positive number") from None
if not math.isfinite(timeout) or timeout <= 0:
raise RuntimeError("REQUEST_TIMEOUT_S must be a finite positive number")
return AppConfig(database_url=url, request_timeout_s=timeout)
def main() -> None:
cfg = load_config({"DATABASE_URL": "postgres://local/app", "REQUEST_TIMEOUT_S": "5"})
print(cfg)
if __name__ == "__main__":
main()
建议运行:
python config_from_env.py
期望输出:
AppConfig(request_timeout_s=5.0)
结合场景再看三个关注点
- 缺必填直接启动失败,避免半残运行。
- frozen 阻止普通字段重新赋值,不是深度不可变或安全边界。repr=False 只隐藏默认 repr 中的 URL,不能阻止手动打印或序列化泄露。
- 本例只验证 URL 非空和超时有限且为正,不验证 URL 协议、凭据或数据库连通性;真实系统按驱动和配置库契约补充。
核心概念与准确模型
- 12-factor:配置存环境
- 分层优先级由应用/配置库定义,例如默认 < 文件 < 环境 < 命令行;Python 不自动采用这套顺序。
- 密钥与普通配置分离
- 特性开关
边界情况与反直觉行为
- 空字符串 vs 未设置。
- 类型转换失败要明确错误。
- 容器与本地路径差异。
常见误区
[!warning] 常见误区:配置散落 os.getenv 到处调用 错误理解:随手读环境。
正确模型:集中加载一次,注入依赖。
工程实践
- 示例
.env.example无密钥 - 启动打印非敏感配置摘要
- 测试注入 env dict
本节总结
配置是产品行为的一部分。集中、校验、保密。
自测题
- 为何启动时校验配置?
- 密钥为何不进仓库?
参考答案
- 失败早、定位易,避免请求中才爆。
- 泄露面与权限失控;应用轮换困难。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| os.environ | 文档 | 环境变量 |
| 12-factor Config | 方法论 | 配置原则 |