在开发Python应用时,我们经常需要处理各种配置信息,比如数据库连接字符串、API密钥、调试模式开关等。这些信息如果直接硬编码在代码中会带来几个严重问题:
我曾在多个项目中遇到过这样的场景:新成员clone代码后无法立即运行,需要逐个询问配置参数;上线时不小心把测试环境的配置推送到生产环境;或者更糟的,把包含生产数据库密码的代码提交到了公开仓库。
Python标准库中的os模块提供了基础的环境变量访问能力:
python复制import os
db_url = os.getenv('DATABASE_URL')
debug = os.getenv('DEBUG', 'False').lower() == 'true'
这种方法虽然简单,但存在明显缺陷:
很多项目会采用单独的config.py或settings.py文件:
python复制# config.py
DATABASE_URL = "postgres://user:pass@localhost/db"
DEBUG = True
这种方式的痛点在于:
首先安装python-dotenv包:
bash复制pip install python-dotenv
项目根目录创建.env文件:
env复制# .env
DATABASE_URL=postgres://user:pass@localhost/db
DEBUG=True
API_KEY=your_api_key_here
在Python中加载:
python复制from dotenv import load_dotenv
load_dotenv() # 默认加载.env文件
# 现在可以通过os模块访问
import os
print(os.getenv('DATABASE_URL'))
load_dotenv()支持多个参数定制行为:
python复制load_dotenv(
dotenv_path='.env.prod', # 指定文件路径
override=True, # 覆盖已存在的环境变量
encoding='utf-8', # 文件编码
verbose=True # 输出加载信息
)
环境变量默认都是字符串,需要手动转换类型:
python复制from dotenv import load_dotenv
import os
load_dotenv()
settings = {
'debug': os.getenv('DEBUG', 'False').lower() == 'true',
'port': int(os.getenv('PORT', '8000')),
'timeout': float(os.getenv('TIMEOUT', '5.0')),
'allowed_hosts': os.getenv('ALLOWED_HOSTS', '').split(',')
}
建议的目录结构:
code复制config/
├── .env.dev
├── .env.test
└── .env.prod
根据环境加载不同文件:
python复制import os
from dotenv import load_dotenv
env = os.getenv('ENVIRONMENT', 'dev')
load_dotenv(f'config/.env.{env}')
gitignore复制# .gitignore
.env
*.env
与Pydantic配合使用示例:
python复制from pydantic import BaseSettings
from dotenv import load_dotenv
load_dotenv()
class Settings(BaseSettings):
db_url: str
debug: bool = False
class Config:
env_file = ".env"
settings = Settings()
python复制from functools import lru_cache
from dotenv import load_dotenv
import os
load_dotenv()
@lru_cache()
def get_config(key):
return os.getenv(key)
在settings.py中添加:
python复制from dotenv import load_dotenv
import os
load_dotenv()
SECRET_KEY = os.getenv('DJANGO_SECRET_KEY')
DEBUG = os.getenv('DEBUG') == 'True'
python复制from flask import Flask
from dotenv import load_dotenv
import os
load_dotenv()
app = Flask(__name__)
app.config['SECRET_KEY'] = os.getenv('FLASK_SECRET_KEY')
app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('DATABASE_URL')
Celery配置示例:
python复制from celery import Celery
from dotenv import load_dotenv
import os
load_dotenv()
app = Celery('tasks')
app.conf.broker_url = os.getenv('CELERY_BROKER_URL')
app.conf.result_backend = os.getenv('CELERY_RESULT_BACKEND')
| 工具/方案 | 优点 | 缺点 |
|---|---|---|
| python-dotenv | 简单易用,零依赖 | 仅适用于开发环境 |
| Django-environ | Django深度集成 | 仅适用于Django项目 |
| Dynaconf | 功能强大,支持多格式 | 学习曲线较陡 |
| 系统环境变量 | 生产环境标准做法 | 开发环境配置不便 |
bash复制# 设置文件权限
chmod 600 .env
python复制# 密钥版本控制示例
current_key = os.getenv('API_KEY_V2')
legacy_key = os.getenv('API_KEY_V1')
使用unittest.mock模拟环境变量:
python复制from unittest.mock import patch
import os
@patch.dict(os.environ, {'DEBUG': 'True'})
def test_debug_mode():
assert os.getenv('DEBUG') == 'True'
创建专门的测试环境文件.env.test:
env复制# .env.test
TESTING=True
DATABASE_URL=sqlite:///:memory:
在pytest中自动加载:
python复制# conftest.py
import pytest
from dotenv import load_dotenv
@pytest.fixture(scope='session', autouse=True)
def load_test_env():
load_dotenv('.env.test')
python复制from pydantic_settings import BaseSettings
class Settings(BaseSettings):
db_url: str
debug: bool = False
class Config:
env_file = ".env"
settings = Settings()
这些方案更适合生产环境,提供加密存储、访问控制、自动轮换等高级功能。