多个python服务共用config.py会导致配置耦合、模块单例污染、语义冲突、热重载失效及测试隔离失败;应改用环境变量+pydantic_settings实现进程级隔离与类型安全。

多个 Python 服务共用同一份 config.py 文件会出什么问题
直接共享一个 config.py,看似省事,实际是把配置耦合进代码路径里,一改全崩。最典型的现象是:A 服务升级后要求 DATABASE_URL 必须带 ?sslmode=require,B 服务连的是内网 postgresql,加了就直接连不上——但没人记得去改 B 的部署环境变量或配置入口。
根本原因在于 Python 的模块导入机制:import config 是单例加载,一旦被任一服务初始化(比如调用了 config.load()),后续所有服务进程都会复用这个模块状态,包括已解析的字典、缓存的连接对象、甚至全局 logger 配置。
- 不同服务对同一配置项语义理解可能不一致(比如
TIMEOUT对 API 网关是响应超时,对定时任务却是重试间隔) - 热重载失效:修改
config.py后,只有重启服务才生效,无法做到配置中心式的动态推送 - 测试隔离失败:单元测试跑着跑着读到了开发机上
config.py里的真实 DB 地址
用 os.environ + pydantic_settings 替代硬编码配置文件
环境变量是进程级隔离的天然边界,每个服务启动时通过 ENV=prod python main.py 或容器 env: 注入,互不影响。配合 pydantic_settings.BaseSettings 可做类型校验和默认值兜底,比手写 os.getenv('X', 'default') 更可靠。
示例结构:
立即学习“Python免费学习笔记(深入)”;
class Settings(BaseSettings): DATABASE_URL: str LOG_LEVEL: str = "INFO" CACHE_TTL_SECONDS: int = 300 <p>settings = Settings() # 自动从 os.environ 读取
- 必须显式声明字段类型,
int字段若传入字符串会抛ValidationError,而不是静默转成 0 - 支持前缀分组:
Settings(_env_prefix="API_")只读API_DATABASE_URL这类变量 - 不推荐用
.env文件:它本质是把环境变量写死在磁盘,和共享config.py没本质区别,仅适合本地开发
多服务间配置复用该怎么做
复用不是指“用同一个文件”,而是“用同一套定义规则”。比如所有服务都需要连接 redis,那就定义一个共享的 Pydantic 模型包 shared_config,里面只放字段声明,不放值:
class RedisConfig(BaseModel): host: str port: int = 6379 db: int = 0
各服务各自实现自己的 Settings,组合复用:
class APISettings(BaseSettings): redis: RedisConfig # 其他专属字段...
- 共享模型包可发为私有 PyPI 包,版本号控制兼容性(如 v1.2 不删字段,只加可选字段)
- 禁止在共享模型里写
default_factory或依赖运行时上下文的逻辑(比如自动读/etc/secrets) - 如果某服务需要覆盖某个字段行为(如测试环境强制走 mock),应在自身
Settings中重定义,而不是改共享模型
configparser 和 yaml.load() 在多服务场景下的隐患
这两种方式本身不坏,但容易让人误以为“只要文件不共享就安全”——其实不然。问题出在加载时机和作用域。
常见错误:
- 在模块顶层执行
config = configparser.ConfigParser(); config.read("conf.ini"):一旦被任一服务 import,就变成全局单例 - 用
yaml.load(open(...))且没设Loader=yaml.CSafeLoader:YAML 的!!python/Object标签可能反序列化任意类,线上服务若配置文件被污染(比如 CI/CD 覆盖出错),直接 RCE - YAML 中写
host: ${HOSTNAME}却没配yaml.load的变量替换逻辑:运行时报KeyError,但错误堆栈指向 YAML 解析层,排查困难
真要用 YAML,至少加一层封装:
def load_yaml_config(path: str) -> dict: with open(path) as f: return yaml.load(f, Loader=yaml.CSafeLoader)
配置不是越集中越好,而是越靠近使用它的服务越可控。共享的应该是结构定义,不是值;隔离的应该是加载动作,不是文件路径。