Python 项目配置管理:从硬编码到 Pydantic Settings
每个 Python 项目都要处理配置。从最简单的 settings.py 到复杂的多环境管理,配置管理的方式直接影响到项目的可维护性和安全性。

这篇文章不讲理论,直接从真实项目的配置演进出发,讲清楚配置管理到底怎么做才是对的。
一、初级阶段:硬编码
新手最常见的写法——配置直接写在代码里:
python
# config.py
DB_HOST = "localhost"
DB_PORT = 3306
DB_USER = "root"
DB_PASS = "123456"
API_KEY = "sk-xxxxxxxxxxxxx"
REDIS_URL = "redis://localhost:6379/0"
在代码中直接引用:
python
from config import DB_HOST, DB_PASS
def connect():
return f"连接 {DB_HOST} with {DB_PASS}"
问题:
API Key、数据库密码等敏感信息明文存储在代码仓库中
本地、测试、生产环境切换时需要手动改代码
配置变更需要重新部署整个应用
团队成员间共享配置文件容易造成冲突
二、中级阶段:环境变量
用环境变量替代硬编码:
python
import os
DB_HOST = os.getenv("DB_HOST", "localhost")
DB_PORT = int(os.getenv("DB_PORT", "3306"))
DB_USER = os.getenv("DB_USER", "root")
DB_PASS = os.getenv("DB_PASS")
API_KEY = os.getenv("API_KEY")
if not API_KEY:
raise ValueError("API_KEY 环境变量未设置")
配合 .env 文件(不提交到 Git):
text
# .env
DB_HOST=localhost
DB_PORT=3306
DB_USER=root
DB_PASS=123456
API_KEY=sk-xxxxxxxxx
加载 .env:
bash
pip install python-dotenv
python
from dotenv import load_dotenv
load_dotenv()
优势:
敏感信息脱离代码仓库
不同环境用不同的
.env文件(.env.dev、.env.prod)符合 12-Factor App 原则
仍存在的问题:
配置项一多,
os.getenv()散落各处类型转换需要手动处理(
int()、bool()、list())缺少配置校验,漏设变量运行时才报错
嵌套配置(如数据库、Redis 等多组相关配置)组织混乱
三、进阶阶段:Pydantic Settings
Pydantic Settings 将环境变量自动解析为类型安全的配置对象。这是目前 Python 项目配置管理的最佳实践。
安装
bash
pip install pydantic-settings
基础用法
python
from pydantic_settings import BaseSettings
from pydantic import Field
class Settings(BaseSettings):
"""应用配置"""
# 字段名默认对应环境变量名(大写)
database_url: str = Field(alias="DATABASE_URL")
api_key: str = Field(alias="API_KEY")
debug: bool = Field(default=False, alias="DEBUG")
max_connections: int = Field(default=100, alias="MAX_CONNECTIONS")
allowed_hosts: list[str] = Field(default=[], alias="ALLOWED_HOSTS")
class Config:
env_file = ".env"
env_file_encoding = "utf-8"
# 使用时自动加载
settings = Settings()
print(settings.database_url)
print(settings.max_connections) # int 类型
嵌套配置
真实项目配置通常有层级结构:
python
from pydantic import BaseModel
from pydantic_settings import BaseSettings
class DatabaseSettings(BaseModel):
host: str = "localhost"
port: int = 3306
user: str = "root"
password: str
database: str = "app_db"
@property
def url(self) -> str:
return f"mysql://{self.user}:{self.password}@{self.host}:{self.port}/{self.database}"
class RedisSettings(BaseModel):
host: str = "localhost"
port: int = 6379
db: int = 0
@property
def url(self) -> str:
return f"redis://{self.host}:{self.port}/{self.db}"
class Settings(BaseSettings):
database: DatabaseSettings
redis: RedisSettings
debug: bool = False
secret_key: str
class Config:
env_file = ".env"
env_nested_delimiter = "__" # 嵌套分隔符
对应的 .env 文件:
text
# 嵌套配置用 __ 分隔
database__host=localhost
database__port=3306
database__user=root
database__password=123456
database__database=myapp
redis__host=localhost
redis__port=6379
debug=True
secret_key=your-secret-key-here
使用:
python
settings = Settings()
print(settings.database.url) # 自动拼接
print(settings.redis.url)
四、多环境管理
不同环境(开发、测试、生产)配置不同:
python
from pydantic_settings import BaseSettings
from typing import Optional
class Settings(BaseSettings):
env: str = "development"
debug: bool = False
database_url: str
api_key: str
class Config:
env_file = f".env.{env}" # 动态加载对应文件
或者用 pydantic-settings 的 EnvSettingsSource 实现更灵活的加载逻辑:
python
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
env: str = "development"
debug: bool = False
database_url: str
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
extra="ignore",
)
# 通过环境变量 ENV=production 切换
# 加载顺序:.env → .env.{env} → 系统环境变量
五、配置校验:提前暴露问题
Pydantic 最强大的能力是配置校验:
python
from pydantic import field_validator
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
database_url: str
max_connections: int = 100
log_level: str = "INFO"
rate_limit_per_minute: int = 60
@field_validator("max_connections")
def validate_max_connections(cls, v):
if v < 1:
raise ValueError("max_connections 必须大于 0")
if v > 500:
raise ValueError("max_connections 不能超过 500")
return v
@field_validator("log_level")
def validate_log_level(cls, v):
allowed = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
if v.upper() not in allowed:
raise ValueError(f"log_level 必须是 {allowed} 之一")
return v.upper()
配置错误在应用启动时就会失败,而不是运行时才暴露。
六、结合不同配置源
有些配置来自文件,有些来自环境变量,有些来自 Vault 等密钥服务:
python
from pydantic_settings import BaseSettings
import json
class Settings(BaseSettings):
database_url: str
redis_url: str
secret_key: str
feature_flags: dict[str, bool] = {}
class Config:
# 可以从多个源加载
env_file = ".env"
env_file_encoding = "utf-8"
@classmethod
def from_file(cls, path: str) -> "Settings":
with open(path) as f:
data = json.load(f)
return cls(**data)
七、生产环境示例
一个完整的生产环境配置管理:
text
project/
├── .env # 公共配置(提交)
├── .env.local # 本地覆盖(不提交)
├── .env.production # 生产环境(不提交,由 CI/CD 注入)
├── config/
│ ├── __init__.py
│ ├── settings.py # Pydantic Settings 定义
│ └── logging.py # 日志配置
└── app/
└── main.py
settings.py:
python
from functools import lru_cache
from pydantic_settings import BaseSettings
from pydantic import Field
class Settings(BaseSettings):
# 应用
app_name: str = "MyApp"
debug: bool = Field(default=False)
env: str = Field(default="development")
# 数据库
database_url: str
database_pool_size: int = Field(default=10)
database_max_overflow: int = Field(default=20)
# Redis
redis_url: str
# 外部服务
api_base_url: str
api_timeout: int = Field(default=30)
# 安全
secret_key: str
jwt_expire_minutes: int = Field(default=60 * 24 * 7)
class Config:
env_file = ".env"
env_file_encoding = "utf-8"
case_sensitive = False
@lru_cache
def get_settings() -> Settings:
return Settings()
main.py 中使用:
python
from config.settings import get_settings
settings = get_settings()
app = FastAPI(debug=settings.debug)
lru_cache 确保 Settings 只初始化一次(单例模式)。
八、踩坑记录
1. 环境变量名大小写
Pydantic Settings 默认区分大小写。如果环境变量是 DEBUG=true,字段名 debug 默认匹配不上。解决方法:
python
class Settings(BaseSettings):
debug: bool = False
class Config:
case_sensitive = False # 不区分大小写
2. 嵌套配置分隔符
用 __ 作为嵌套分隔符时,注意不要和环境变量中的真实 __ 冲突。
python
# 如果环境变量本身就包含 __,需要自定义分隔符
class Settings(BaseSettings):
database: DatabaseSettings
class Config:
env_nested_delimiter = "___" # 自定义为三个下划线
3. List 类型的环境变量
python
allowed_hosts: list[str] = []
# .env 文件写法(用逗号分隔)
ALLOWED_HOSTS=localhost,127.0.0.1,example.com
九、总结
配置管理演进路径:
阶段方案优点缺点初级硬编码简单不安全、难维护中级os.getenv() + .env配置与代码分离类型不安全、组织混乱进阶Pydantic Settings类型安全、校验、自动补全需要学习成本
建议:新项目直接从 Pydantic Settings 起步。它已经是 Python 配置管理的事实标准,不仅解决了配置加载的问题,还提供了类型校验、嵌套配置、多源合并等能力。花 10 分钟配置好,后续维护省下的时间远不止这些。
本文为纯技术分享,不涉及任何品牌或产品。
