Python配置管理完全指南:从.env到Pydantic BaseSettings

2026 年 01 月 25 日
13 次浏览
20398 字数

📚 学习目标

通过本教程,你将掌握:

  • 为什么需要配置管理
  • 如何使用传统 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️⃣ 最佳实践总结

🎯 配置管理原则

  1. 分离配置和代码
  2. 不同环境不同配置
  3. 敏感信息保密
  4. 配置可验证

📁 项目结构建议

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 文件?

  1. 添加到 .gitignore
  2. 提供 .env.example 模板
  3. 使用环境变量替代(生产环境)

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 位)
  • 有效的邮箱格式
  • 安全的密钥格式

📚 扩展学习

推荐阅读

  1. Pydantic官方文档
  2. The Twelve-Factor App: Config
  3. Python最佳实践:配置管理

相关工具

  • Dynaconf:更复杂的配置管理
  • Hydra:Facebook 的配置管理
  • configparser:Python 标准库,适合 INI 文件

🎯 总结

方面 python-dotenv Pydantic BaseSettings
学习曲线 平缓 中等
类型安全 ❌ 手动 ✅ 自动
验证支持 ❌ 无 ✅ 强大
IDE 支持 有限 优秀
适合场景 脚本/小型项目 应用/大型项目
维护成本

核心建议

  • 从今天开始,所有新项目都使用 Pydantic BaseSettings
  • 现有项目在重构时逐步迁移到 Pydantic
  • 始终记住:配置应该 " 声明式 " 而非 " 命令式 "

配置管理是软件质量的基石。一个好的配置系统能让你的代码更健壮、更安全、更易维护。现在就去实践吧!


记得:代码中的魔法数字是坏味道,配置管理是解药!🍃

暂无标签