Python 配置管理实战——dynaconf、python-decouple、多环境配置最佳实践
Python 配置管理实战——dynaconf、python-decouple、多环境配置最佳实践
适读人群:需要管理开发/测试/生产多套环境配置的 Python 工程师 | 阅读时长:约15分钟 | 核心价值:建立安全、灵活、可维护的配置管理体系
那次配置写死在代码里的惨痛经历
刚工作那两年,我接手过一个老项目,代码里到处都是这样的写法:
DB_HOST = "192.168.1.100"
DB_PASSWORD = "db_password_2019"
REDIS_URL = "redis://192.168.1.101:6379"
SECRET_KEY = "my-secret-key-hardcoded"有一次需要部署到测试环境,我才意识到:生产和测试的数据库是不一样的,但配置全写死在代码里,改一次就要提交一次代码……不仅低效,安全性也是一团糟。
更糟糕的是,这个项目是开源的,某天有个同事不小心把带有生产密码的代码推到了 GitHub,当天晚上生产数据库就被人访问了,紧急改了密码才没造成数据泄露。
从那以后,我开始认真研究配置管理,形成了一套行之有效的实践方案。今天来系统讲讲。
一、配置管理的几个核心原则
在讲工具之前,先说原则——原则比工具更重要:
- 敏感信息绝不进代码库:密码、密钥、Token 通过环境变量或密钥管理服务读取
- 环境差异只在配置里:开发/测试/生产的差异不应该体现在代码逻辑里
- 配置有默认值:开发环境的默认值应该让人能快速启动,不需要额外配置
- 配置有类型校验:配置项有明确类型,启动时就校验,而不是运行时崩溃
二、python-decouple——轻量级首选
pip install python-decouple对于中小型项目,python-decouple 是我最推荐的方案,极轻量,学习成本低:
# config.py
from decouple import config, Csv, Choices
# 基础读取:从环境变量或 .env 文件读取
DATABASE_URL = config("DATABASE_URL")
# 带默认值和类型转换
DEBUG = config("DEBUG", default=False, cast=bool)
PORT = config("PORT", default=8000, cast=int)
ALLOWED_HOSTS = config("ALLOWED_HOSTS", default="localhost", cast=Csv())
# 带校验
LOG_LEVEL = config(
"LOG_LEVEL",
default="INFO",
cast=Choices(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"])
)
# 可选配置
SENTRY_DSN = config("SENTRY_DSN", default=None)
# .env 文件(不提交到 Git)
# DATABASE_URL=postgresql://user:pass@localhost/mydb
# DEBUG=True
# PORT=8080
# ALLOWED_HOSTS=localhost,127.0.0.1
# LOG_LEVEL=DEBUG
# .env.example(提交到 Git,供团队参考)
# DATABASE_URL=postgresql://user:password@localhost/mydb_dev
# DEBUG=False
# PORT=8000多环境配置文件
.env # 本地开发(不提交 Git)
.env.test # 测试环境配置
.env.example # 配置模板(提交 Git)
settings/
base.py # 公共配置
development.py # 开发环境
testing.py # 测试环境
production.py # 生产环境# settings/base.py
from decouple import config
# 公共配置(所有环境共享)
SECRET_KEY = config("SECRET_KEY")
DATABASE_URL = config("DATABASE_URL")
REDIS_URL = config("REDIS_URL", default="redis://localhost:6379/0")
# 应用配置(有合理默认值)
PAGINATION_SIZE = config("PAGINATION_SIZE", default=20, cast=int)
CACHE_TTL = config("CACHE_TTL", default=300, cast=int)
# settings/development.py
from .base import *
DEBUG = True
LOG_LEVEL = "DEBUG"
DATABASE_POOL_SIZE = 5
# settings/production.py
from .base import *
DEBUG = False
LOG_LEVEL = "WARNING"
DATABASE_POOL_SIZE = 20
# 生产环境额外配置
SENTRY_DSN = config("SENTRY_DSN")
# 根据环境变量加载对应配置
import os
import importlib
env = os.getenv("APP_ENV", "development")
settings = importlib.import_module(f"settings.{env}")三、dynaconf——企业级配置管理
pip install dynaconfdynaconf 支持多种配置来源(env、.toml、.yaml、.json),支持配置层叠、动态验证,适合复杂项目:
# config.py
from dynaconf import Dynaconf, Validator
settings = Dynaconf(
# 环境变量前缀(避免冲突)
envvar_prefix="MYAPP",
# 配置文件(按顺序层叠)
settings_file=["settings.toml", ".secrets.toml"],
# 环境切换变量
environments=True,
env_switcher="MYAPP_ENV",
# 从环境变量加载(覆盖文件配置)
load_dotenv=True,
# 类型校验
validators=[
Validator("DATABASE_URL", must_exist=True),
Validator("SECRET_KEY", must_exist=True, len_min=32),
Validator("PORT", default=8000, gte=1024, lte=65535),
Validator("LOG_LEVEL", default="INFO",
is_in=["DEBUG", "INFO", "WARNING", "ERROR"]),
Validator("DEBUG", default=False, is_type_of=bool),
],
)
# 触发校验
settings.validators.validate_all()# settings.toml
[default]
PORT = 8000
LOG_LEVEL = "INFO"
DEBUG = false
PAGINATION_SIZE = 20
[development]
DEBUG = true
LOG_LEVEL = "DEBUG"
DATABASE_URL = "postgresql://localhost/myapp_dev"
[testing]
DATABASE_URL = "postgresql://localhost/myapp_test"
REDIS_URL = "redis://localhost:6379/1"
[production]
LOG_LEVEL = "WARNING"
DATABASE_POOL_SIZE = 20# .secrets.toml(不提交 Git!)
[default]
SECRET_KEY = "your-secret-key-here"
DATABASE_URL = "postgresql://user:password@localhost/myapp"
REDIS_URL = "redis://:password@redis-host:6379/0"
[production]
DATABASE_URL = "postgresql://user:prod-password@prod-db:5432/myapp"# 使用配置
from config import settings
print(settings.DATABASE_URL)
print(settings.PORT)
print(settings.DEBUG)
# 支持点号嵌套访问
# settings.DATABASE.HOST(如果配置是嵌套结构)四、Pydantic Settings——类型安全的配置管理
pip install pydantic-settings如果项目已经用了 Pydantic(比如 FastAPI 项目),pydantic-settings 是最自然的选择:
from pydantic import Field, field_validator, model_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
from typing import Optional
import logging
class DatabaseSettings(BaseSettings):
host: str = "localhost"
port: int = 5432
name: str = "myapp"
user: str
password: str
pool_size: int = Field(default=10, ge=1, le=100)
@property
def url(self) -> str:
return f"postgresql://{self.user}:{self.password}@{self.host}:{self.port}/{self.name}"
model_config = SettingsConfigDict(env_prefix="DB_")
class AppSettings(BaseSettings):
# 环境标识
env: str = Field(default="development", pattern="^(development|testing|production)$")
debug: bool = False
secret_key: str = Field(min_length=32)
port: int = Field(default=8000, ge=1024, le=65535)
allowed_hosts: list[str] = ["localhost", "127.0.0.1"]
log_level: str = "INFO"
# 嵌套配置
database: DatabaseSettings = DatabaseSettings()
# 可选服务
sentry_dsn: Optional[str] = None
redis_url: str = "redis://localhost:6379/0"
@field_validator("log_level")
@classmethod
def validate_log_level(cls, v):
v = v.upper()
assert v in ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL")
return v
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
env_nested_delimiter="__", # DB__HOST=localhost 对应 database.host
case_sensitive=False,
)
# 单例模式
_settings: Optional[AppSettings] = None
def get_settings() -> AppSettings:
global _settings
if _settings is None:
_settings = AppSettings()
return _settings
# FastAPI 依赖注入
from fastapi import Depends
def get_app_settings(settings: AppSettings = Depends(get_settings)):
return settings五、踩坑实录
踩坑实录1:.env 文件提交到了 Git
现象:.env 文件被 git add . 一起提交了。
原因:忘记在 .gitignore 里加 .env。
解法:项目创建时就配置好 .gitignore,并加 pre-commit hook 检查。
# .gitignore 必须包含
.env
.env.local
.secrets.toml
*.key
*.pem踩坑实录2:环境变量类型不对导致 AttributeError
现象:if settings.DEBUG: 报错,或者 DEBUG=True 比较失效。
原因:环境变量全是字符串,DEBUG="True" 不等于 True。
解法:使用 cast=bool 或 Pydantic 类型声明,确保类型转换正确处理。
踩坑实录3:配置项名字冲突导致读错了值
现象:程序读到了系统环境变量的值而不是项目配置值。
原因:配置变量名太通用(如 PORT、ENV),和系统环境变量冲突。
解法:使用项目专属前缀(MYAPP_PORT、MYAPP_ENV)。
六、选型建议
| 项目规模 | 推荐方案 |
|---|---|
| 小型脚本/工具 | python-decouple |
| 中型 Web 项目 | python-decouple 或 dynaconf |
| FastAPI/Pydantic 项目 | pydantic-settings |
| 大型企业项目 | dynaconf + 密钥管理服务(KMS/Vault) |
| 云原生(K8s) | 环境变量 + ConfigMap + Secret |
一个原则优先于所有工具选择:敏感配置不进代码库,所有环境用环境变量注入。这是底线,其他都是技巧。
