Python配置管理完全指南:从.env到Pydantic BaseSettings
📚 学习目标
通过本教程,你将掌握:
- 为什么需要配置管理
- 如何使用传统 dotenv 方法
- 如何使用现代的 Pydantic BaseSettings
- 如何根据项目需求选择合适的方案
- 配置管理的最佳实践
1️⃣ 什么是配置管理?为什么需要它?
现实世界类比
想象你在经营一家连锁咖啡店:
- 配方 = 代码逻辑
- 原料用量 = 配置参数(咖啡豆克数、牛奶毫升数、水温等)
- 不同分店 = 不同环境(开发、测试、生产)
每家分店可能有:
- 不同的咖啡机(数据库)
- 不同的营业时间(功能开关)
- 不同的库存量(资源限制)
如果把配方和用量都写死,每次调整都需要重新培训员工(修改代码)。配置管理就是把这些 " 用量 " 单独拿出来管理。
代码中的问题
# ❌ 硬编码配置(不要这样做!)
DATABASE_URL = "postgresql://user:password@localhost/mydb"
API_KEY = "sk_live_123456789"
DEBUG = True
PORT = 8000
# 问题:
# 1. 敏感信息泄露(API_KEY在代码中)
# 2. 每个环境需要不同的代码
# 3. 修改配置需要重新部署
2️⃣ 传统方法:python-dotenv
2.1 什么是 dotenv?
python-dotenv 是一个 Python 库,允许你从 .env 文件中读取环境变量。
2.2 基本使用步骤
步骤 1:安装
pip install python-dotenv
步骤 2:创建.env 文件
# .env 文件
DATABASE_URL=postgresql://user:password@localhost/mydb
DEBUG=True
PORT=8000
SECRET_KEY=mysecretkey123
步骤 3:在 Python 中加载
# config.py
import os
from dotenv import load_dotenv
# 加载.env文件
load_dotenv()
# 读取配置
class Config:
DATABASE_URL = os.getenv("DATABASE_URL")
DEBUG = os.getenv("DEBUG") == "True" # 需要手动转换
PORT = int(os.getenv("PORT", 8000)) # 设置默认值
SECRET_KEY = os.getenv("SECRET_KEY")
config = Config()
2.3 实际例子:数据库连接
# database.py
import psycopg2
from config import config
def connect_to_db():
"""连接到数据库"""
try:
# 直接使用配置
connection = psycopg2.connect(config.DATABASE_URL)
print("数据库连接成功!")
return connection
except Exception as e:
print(f"连接失败: {e}")
return None
# 使用
conn = connect_to_db()
2.4 Dotenv 的优缺点
✅ 优点:
- 简单易学
- 代码量少
- 广泛支持
- 与 Shell 环境变量兼容
❌ 缺点:
- 类型转换需要手动处理
- 没有验证机制
- 容易出错
- IDE 支持有限
3️⃣ 现代方法:Pydantic BaseSettings
3.1 什么是 Pydantic?
Pydantic 是一个数据验证和设置管理库,使用 Python 类型提示。
3.2 为什么选择 Pydantic?
# ❌ 传统方式的问题
debug_value = os.getenv("DEBUG")
# 用户可能输入:True、true、1、"1"、yes...
# 你需要处理所有情况
# ✅ Pydantic自动处理
from pydantic import BaseSettings
class Settings(BaseSettings):
DEBUG: bool # 自动转换!
3.3 完整教程
步骤 1:安装
pip install pydantic-settings
步骤 2:创建配置类
# settings.py
from pydantic_settings import BaseSettings
from pydantic import Field, validator
from typing import List, Optional
class Settings(BaseSettings):
# 基础配置(带默认值)
APP_NAME: str = "My Awesome App"
DEBUG: bool = False
PORT: int = Field(8000, ge=1, le=65535) # 验证:1-65535
# 数据库配置(必填)
DATABASE_HOST: str
DATABASE_PORT: int = 5432
DATABASE_USER: str
DATABASE_PASSWORD: str
DATABASE_NAME: str = "mydb"
# 列表类型
ALLOWED_HOSTS: List[str] = ["localhost", "127.0.0.1"]
# 可选配置
REDIS_URL: Optional[str] = None
class Config:
# 从.env文件加载
env_file = ".env"
# 环境变量不区分大小写
case_sensitive = False
@property
def database_url(self) -> str:
"""动态生成数据库URL"""
return (
f"postgresql://{self.DATABASE_USER}:{self.DATABASE_PASSWORD}"
f"@{self.DATABASE_HOST}:{self.DATABASE_PORT}/{self.DATABASE_NAME}"
)
@validator("ALLOWED_HOSTS")
def validate_hosts(cls, v):
"""验证主机列表"""
if not v:
raise ValueError("至少需要一个允许的主机")
return v
# 创建全局配置实例
settings = Settings()
步骤 3:使用配置
# app.py
from fastapi import FastAPI
from settings import settings
app = FastAPI(
title=settings.APP_NAME,
debug=settings.DEBUG,
)
@app.get("/")
def read_root():
return {
"app_name": settings.APP_NAME,
"debug_mode": settings.DEBUG,
"database": settings.database_url,
}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=settings.PORT)
3.4 进阶功能
🔐 敏感信息保护
from pydantic import SecretStr, SecretBytes
class Settings(BaseSettings):
# 密码在打印时会隐藏
API_SECRET: SecretStr
ENCRYPTION_KEY: SecretBytes
def get_api_secret(self) -> str:
"""获取真实的秘密值"""
return self.API_SECRET.get_secret_value()
🌳 嵌套配置
from pydantic import BaseModel
class DatabaseConfig(BaseModel):
host: str
port: int = 5432
pool_size: int = 10
echo: bool = False
class APIConfig(BaseModel):
timeout: int = 30
retries: int = 3
class Settings(BaseSettings):
database: DatabaseConfig = DatabaseConfig(host="localhost")
api: APIConfig = APIConfig()
class Config:
env_nested_delimiter = "__" # 使用DATABASE__HOST格式
# .env文件:
# DATABASE__HOST=db.example.com
# DATABASE__POOL_SIZE=20
# API__TIMEOUT=60
⚡ 环境特定配置
from typing import Literal
class Settings(BaseSettings):
ENVIRONMENT: Literal["dev", "test", "prod"] = "dev"
# 根据不同环境设置不同值
@property
def is_production(self) -> bool:
return self.ENVIRONMENT == "prod"
@property
def log_level(self) -> str:
levels = {"dev": "DEBUG", "test": "INFO", "prod": "WARNING"}
return levels[self.ENVIRONMENT]
@property
def database_url(self) -> str:
if self.is_production:
return self.PROD_DATABASE_URL
return self.DEV_DATABASE_URL
4️⃣ 实战对比:用户管理系统
让我们用两种方法实现同一个功能。
需求
- 连接 PostgreSQL 数据库
- 设置日志级别
- 配置 API 密钥
- 支持开发和生产环境
4.1 传统 Dotenv 实现
# config_old.py
import os
from dotenv import load_dotenv
load_dotenv()
class Config:
# 读取所有配置
DATABASE_URL = os.getenv("DATABASE_URL")
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")
API_KEY = os.getenv("API_KEY")
# 手动类型转换
DEBUG = os.getenv("DEBUG", "False").lower() in ("true", "1", "yes")
MAX_CONNECTIONS = int(os.getenv("MAX_CONNECTIONS", "10"))
# 手动验证
@staticmethod
def validate():
errors = []
if not Config.DATABASE_URL:
errors.append("DATABASE_URL未设置")
if not Config.API_KEY:
errors.append("API_KEY未设置")
if Config.MAX_CONNECTIONS < 1:
errors.append("MAX_CONNECTIONS必须大于0")
if errors:
raise ValueError(f"配置错误: {', '.join(errors)}")
# 验证配置
config = Config()
config.validate()
# 问题:
# 1. 代码冗长
# 2. 验证逻辑分散
# 3. 新配置需要修改多处
4.2 Pydantic BaseSettings 实现
# config_new.py
from pydantic_settings import BaseSettings
from pydantic import Field, validator
from typing import Optional
class Settings(BaseSettings):
# 声明式配置
DATABASE_URL: str = Field(..., min_length=1)
LOG_LEVEL: str = Field("INFO", pattern="^(DEBUG|INFO|WARNING|ERROR)$")
API_KEY: str
DEBUG: bool = False
MAX_CONNECTIONS: int = Field(10, ge=1, le=100)
ENVIRONMENT: str = Field("dev", pattern="^(dev|test|prod)$")
# 可选配置
REDIS_URL: Optional[str] = None
class Config:
env_file = ".env"
env_file_encoding = "utf-8"
@validator("DATABASE_URL")
def validate_db_url(cls, v):
"""验证数据库URL格式"""
if not v.startswith("postgresql://"):
raise ValueError("必须是PostgreSQL数据库URL")
return v
@property
def is_production(self):
return self.ENVIRONMENT == "prod"
@property
def connection_params(self):
"""生成连接参数"""
return {
"url": self.DATABASE_URL,
"max_connections": self.MAX_CONNECTIONS,
"echo": self.DEBUG,
}
# 自动验证和类型转换
settings = Settings()
# 优点:
# 1. 简洁明了
# 2. 自动验证
# 3. IDE智能提示
# 4. 易于扩展
5️⃣ 迁移指南:从 Dotenv 到 Pydantic
步骤 1:分析现有配置
# 旧的config.py
import os
from dotenv import load_dotenv
load_dotenv()
class Config:
DB_HOST = os.getenv("DB_HOST", "localhost")
DB_PORT = int(os.getenv("DB_PORT", 5432))
DB_USER = os.getenv("DB_USER")
DB_PASS = os.getenv("DB_PASS")
DB_NAME = os.getenv("DB_NAME", "mydb")
DEBUG = os.getenv("DEBUG", "False") == "True"
步骤 2:创建新的 Settings 类
# settings.py
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
DB_HOST: str = "localhost"
DB_PORT: int = 5432
DB_USER: str # 必填
DB_PASS: str # 必填
DB_NAME: str = "mydb"
DEBUG: bool = False
class Config:
env_file = ".env"
# 兼容旧的命名
fields = {
"DB_HOST": {"env": "DB_HOST"},
"DB_PORT": {"env": "DB_PORT"},
"DB_USER": {"env": "DB_USER"},
"DB_PASS": {"env": "DB_PASS"},
"DB_NAME": {"env": "DB_NAME"},
"DEBUG": {"env": "DEBUG"},
}
@property
def database_url(self):
return f"postgresql://{self.DB_USER}:{self.DB_PASS}@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}"
步骤 3:逐步迁移
# 过渡方案:两者共存
from dotenv import load_dotenv
from pydantic_settings import BaseSettings
import os
# 保持旧的兼容性
load_dotenv()
old_config = {
"DB_HOST": os.getenv("DB_HOST"),
# ...
}
# 使用新的配置
class Settings(BaseSettings):
# 新配置
pass
# 并行运行,验证一致性
6️⃣ 最佳实践总结
🎯 配置管理原则
- 分离配置和代码
- 不同环境不同配置
- 敏感信息保密
- 配置可验证
📁 项目结构建议
my_project/
├── .env.example # 示例配置
├── .env # 本地开发配置(.gitignore)
├── config/
│ ├── __init__.py
│ ├── settings.py # 主要配置类
│ ├── development.py # 开发配置
│ ├── production.py # 生产配置
│ └── testing.py # 测试配置
├── src/
│ └── app.py
└── docker-compose.yml
🔧 环境管理
# .env文件示例
# 开发环境
ENVIRONMENT=development
DEBUG=True
DATABASE_URL=postgresql://localhost/dev_db
# 测试环境(.env.test)
ENVIRONMENT=testing
DEBUG=False
DATABASE_URL=postgresql://localhost/test_db
# 生产环境(通过Docker/K8s设置)
ENVIRONMENT=production
DEBUG=False
DATABASE_URL=postgresql://prod-db/production
🚀 部署策略
# 根据环境自动选择配置
import os
env = os.getenv("ENVIRONMENT", "development")
if env == "production":
from config.production import settings
elif env == "testing":
from config.testing import settings
else:
from config.development import settings
7️⃣ 常见问题解答
Q1:什么时候用 dotenv?什么时候用 Pydantic?
- 小型脚本/快速原型:用 dotenv
- Web 应用/API 服务:用 Pydantic
- 团队项目/生产环境:一定要用 Pydantic
Q2:如何保护.env 文件?
- 添加到
.gitignore - 提供
.env.example模板 - 使用环境变量替代(生产环境)
Q3:Pydantic 会影响性能吗?
几乎不会。配置只在启动时加载一次,对运行时性能无影响。
Q4:如何在 Docker 中使用?
# Dockerfile
FROM python:3.11
# 复制代码
COPY . /app
WORKDIR /app
# 安装依赖
RUN pip install pydantic-settings
# 通过环境变量传递配置
ENV DATABASE_URL=postgresql://db/production
ENV DEBUG=False
CMD ["python", "app.py"]
🎓 练习任务
任务 1:创建个人博客配置
创建一个博客系统的配置,包含:
- 数据库配置
- Redis 缓存配置
- 邮件服务配置
- 文件上传配置
- 安全配置
任务 2:环境切换
实现一个配置系统,支持:
- 开发环境(本地数据库,调试模式)
- 测试环境(测试数据库,记录详细日志)
- 生产环境(云数据库,最小日志)
任务 3:配置验证
为以下场景添加验证:
- 数据库连接池大小(1-100)
- 密码最小长度(8 位)
- 有效的邮箱格式
- 安全的密钥格式
📚 扩展学习
推荐阅读
相关工具
- Dynaconf:更复杂的配置管理
- Hydra:Facebook 的配置管理
- configparser:Python 标准库,适合 INI 文件
🎯 总结
| 方面 | python-dotenv | Pydantic BaseSettings |
|---|---|---|
| 学习曲线 | 平缓 | 中等 |
| 类型安全 | ❌ 手动 | ✅ 自动 |
| 验证支持 | ❌ 无 | ✅ 强大 |
| IDE 支持 | 有限 | 优秀 |
| 适合场景 | 脚本/小型项目 | 应用/大型项目 |
| 维护成本 | 高 | 低 |
核心建议:
- 从今天开始,所有新项目都使用 Pydantic BaseSettings
- 现有项目在重构时逐步迁移到 Pydantic
- 始终记住:配置应该 " 声明式 " 而非 " 命令式 "
配置管理是软件质量的基石。一个好的配置系统能让你的代码更健壮、更安全、更易维护。现在就去实践吧!
记得:代码中的魔法数字是坏味道,配置管理是解药!🍃