Python 环境变量:os.environ 与 dotenv
Python 应用经常需要访问敏感信息(如 API 密钥、数据库密码)或配置参数(如调试开关、服务地址)。直接把这些值写死在代码里既不安全也不灵活。正确做法是使用环境变量——操作系统提供的键值对存储机制,程序运行时从中读取配置。
Python 标准库的 os.environ 对象能直接读取系统环境变量,而第三方库 python-dotenv 则允许你从 .env 文件加载变量,特别适合本地开发。两者结合使用,既能保证生产环境的安全性,又能简化开发流程。
第一步:理解 os.environ —— 读取系统环境变量
os.environ 是一个类似字典的对象,包含当前进程继承的所有环境变量。
创建一个测试脚本 read_env.py:
import os
# 读取名为 'DATABASE_URL' 的环境变量
db_url = os.environ.get('DATABASE_URL')
print(f"数据库地址: {db_url}")
# 如果变量不存在,get() 返回 None;也可指定默认值
debug_mode = os.environ.get('DEBUG', 'False')
print(f"调试模式: {debug_mode}")
在终端中临时设置环境变量并运行脚本(Linux/macOS):
DATABASE_URL=postgresql://user:pass@localhost/db DEBUG=True python read_env.py
在 Windows PowerShell 中设置并运行:
$env:DATABASE_URL="postgresql://user:pass@localhost/db"; $env:DEBUG="True"; python read_env.py
注意:通过命令行设置的变量仅在当前会话有效,关闭终端后消失。生产环境中,这些变量通常由部署平台(如 Docker、Heroku、Kubernetes)注入。
第二步:引入 dotenv —— 从文件加载环境变量
开发时频繁在命令行输入变量很麻烦。python-dotenv 库让你把变量写进 .env 文件,自动加载到 os.environ。
安装 python-dotenv:
pip install python-dotenv
在项目根目录创建 .env 文件:
# .env
DATABASE_URL=postgresql://dev_user:dev_pass@localhost/myapp_dev
DEBUG=True
SECRET_KEY=my_super_secret_dev_key_123!
API_TIMEOUT=30
注意:
.env文件绝对不能提交到 Git!立即在.gitignore中添加一行:.env
修改 read_env.py 以支持自动加载 .env:
import os
from dotenv import load_dotenv
# 自动从 .env 文件加载变量到 os.environ
load_dotenv()
db_url = os.environ.get('DATABASE_URL')
debug_mode = os.environ.get('DEBUG', 'False')
secret_key = os.environ.get('SECRET_KEY')
api_timeout = int(os.environ.get('API_TIMEOUT', '10')) # 转为整数
print(f"数据库地址: {db_url}")
print(f"调试模式: {debug_mode}")
print(f"密钥: {secret_key}")
print(f"API 超时(秒): {api_timeout}")
直接运行脚本(无需手动设置环境变量):
python read_env.py
load_dotenv() 默认查找当前工作目录下的 .env 文件。如果文件在其他位置,可传入路径:
load_dotenv('/path/to/your/.env')
第三步:处理变量类型与缺失情况
环境变量始终是字符串。需要布尔值、数字等类型时,必须手动转换,并处理无效值。
编写安全的类型转换函数:
import os
from dotenv import load_dotenv
load_dotenv()
def get_bool_env(var_name, default=False):
"""将环境变量转为布尔值"""
val = os.environ.get(var_name)
if val is None:
return default
return val.lower() in ('true', '1', 'yes', 'on')
def get_int_env(var_name, default=0):
"""将环境变量转为整数"""
val = os.environ.get(var_name)
if val is None:
return default
try:
return int(val)
except ValueError:
raise ValueError(f"环境变量 {var_name} 必须是整数,但得到: '{val}'")
# 使用示例
DEBUG = get_bool_env('DEBUG', False)
PORT = get_int_env('PORT', 8000)
MAX_RETRIES = get_int_env('MAX_RETRIES') # 若未设置,默认为 0
永远不要直接用 os.environ['KEY'] 访问——如果变量不存在会抛出 KeyError。始终使用 os.environ.get('KEY', default)。
第四步:区分开发、测试与生产环境
不同环境需要不同配置。可通过一个主 .env 文件配合环境标识实现。
创建多个 .env 文件:
.env(通用默认值).env.development(开发专用).env.testing(测试专用).env.production(生产专用,通常不放在代码库)
在代码中根据 ENV 变量选择加载哪个文件:
import os
from dotenv import load_dotenv
# 先加载通用配置
load_dotenv('.env')
# 再根据 ENV 加载特定环境配置(会覆盖同名变量)
env = os.environ.get('ENV', 'development')
env_file = f".env.{env}"
if os.path.exists(env_file):
load_dotenv(env_file, override=True) # override=True 允许覆盖已有变量
# 现在所有变量都已就绪
DATABASE_URL = os.environ['DATABASE_URL']
DEBUG = get_bool_env('DEBUG')
运行时指定环境:
ENV=production python app.py
生产环境中,通常不使用
.env文件,而是由部署系统直接设置环境变量。此时load_dotenv()不会找到文件,但os.environ已包含所需值,逻辑依然成立。
第五步:安全最佳实践
环境变量虽比硬编码安全,但仍需注意:
- 绝不提交
.env到版本控制:确保.gitignore包含.env*。 - 避免在日志中打印敏感变量:如
SECRET_KEY、密码等。 - 生产环境使用最小权限原则:数据库账号只给必要权限。
- 定期轮换密钥:尤其当怀疑泄露时。
下表总结了常见场景下的推荐做法:
| 场景 | 推荐方式 | 示例命令/文件 |
|---|---|---|
| 本地开发 | .env 文件 + load_dotenv() |
echo "DEBUG=True" > .env |
| CI/CD 测试 | 在 CI 配置中设置环境变量 | GitHub Actions 的 env: 字段 |
| Docker 容器 | docker run -e KEY=VAL 或 --env-file |
docker run --env-file .env.prod |
| 云平台 (如 Heroku) | 在平台仪表盘设置 Config Vars | Heroku Settings → Reveal Config Vars |
第六步:高级技巧与陷阱规避
1. 变量引用与嵌套
.env 文件支持变量引用(需 python-dotenv>=0.19.0):
# .env
BASE_DIR=/app
LOG_PATH=${BASE_DIR}/logs
DB_USER=admin
DB_PASSWORD=secret123
DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@localhost/mydb
```
`load_dotenv()` 会自动解析 `${VAR}` 语法。
### 2. 处理多行值
`.env` 文件本身不支持真正的多行值。若需存储如私钥,可用以下变通:
```env
# 将换行符替换为 \n 字符串
PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nMIIE...A==\n-----END RSA PRIVATE KEY-----"
然后在代码中还原:
private_key = os.environ.get('PRIVATE_KEY', '').replace('\\n', '\n')
3. 验证必填变量
启动时检查关键变量是否存在:
required_vars = ['DATABASE_URL', 'SECRET_KEY']
missing = [var for var in required_vars if not os.environ.get(var)]
if missing:
raise EnvironmentError(f"缺少必需的环境变量: {', '.join(missing)}")
4. 避免污染全局环境
load_dotenv() 默认修改全局 os.environ。如需隔离,可加载到独立字典:
from dotenv import dotenv_values
config = dotenv_values(".env") # 返回 dict,不修改 os.environ
db_url = config['DATABASE_URL']
这种方式适合配置解析器,不影响其他模块。
最终整合:一个健壮的配置加载器
将上述实践封装成可复用的模块 config.py:
# config.py
import os
from typing import Optional
from dotenv import load_dotenv
# 加载 .env 文件
load_dotenv()
class Config:
def __init__(self):
self._validate_required()
def _validate_required(self):
required = ['DATABASE_URL', 'SECRET_KEY']
missing = [var for var in required if not os.environ.get(var)]
if missing:
raise EnvironmentError(f"缺少必需的环境变量: {', '.join(missing)}")
@property
def database_url(self) -> str:
return os.environ['DATABASE_URL']
@property
def debug(self) -> bool:
return os.environ.get('DEBUG', 'False').lower() in ('true', '1', 'yes')
@property
def secret_key(self) -> str:
return os.environ['SECRET_KEY']
@property
def api_timeout(self) -> int:
return int(os.environ.get('API_TIMEOUT', '30'))
# 全局实例
config = Config()
在应用其他部分直接使用:
from config import config
if config.debug:
print("调试模式已启用")
connect_to_db(config.database_url)
暂无评论,快来抢沙发吧!