Skip to main content

python-dotenv

1. python-dotenv 是什么?

python-dotenv 是 Python 中用于管理环境变量的工具,它允许你从 .env 文件加载环境变量到 os.environ 中,让配置管理更简单、更安全。

1.1 为什么需要 python-dotenv?

在开发应用时,经常需要存储敏感信息(如数据库密码、API 密钥等)。如果直接写在代码中,会有以下问题:

  1. 安全问题 :代码可能被提交到版本控制系统,泄露敏感信息
  2. 环境差异 :开发、测试、生产环境需要不同的配置
  3. 管理困难 :修改配置需要改代码,容易出错

使用 python-dotenv 的好处

  • 安全 :敏感信息存储在 .env 文件中,不提交到版本控制
  • 灵活 :不同环境可以使用不同的 .env 文件
  • 简单 :一行代码就能加载所有配置

简单理解

  • 环境变量 :存储在系统或文件中的配置信息
  • .env 文件 :存储环境变量的文本文件
  • python-dotenv :从 .env 文件读取环境变量的工具

1.2 基本概念

# 导入 python-dotenv
from dotenv import load_dotenv
import os

# 加载 .env 文件中的环境变量
load_dotenv()

# 现在可以使用环境变量
api_key = os.getenv('API_KEY')
print(f"API Key: {api_key}")

说明

  • load_dotenv().env 文件加载环境变量
  • os.getenv() 获取环境变量的值
  • 环境变量会被加载到 os.environ

2. 前置知识

在学习 python-dotenv 之前,你需要了解以下基础知识。

2.1 什么是环境变量?

环境变量(Environment Variables) 是存储在操作系统中的键值对,用于配置应用程序。

简单理解

  • 环境变量就像是一个全局的配置字典
  • 键(Key)是变量名,值(Value)是变量值
  • 程序可以从环境变量中读取配置

在 Python 中使用环境变量

# 导入 os 模块
import os

# 获取环境变量
# getenv() 获取环境变量的值,如果不存在返回 None
api_key = os.getenv('API_KEY')

# 获取环境变量(带默认值)
# 如果环境变量不存在,返回默认值
port = os.getenv('PORT', '8000')

# 直接访问 os.environ(字典格式)
# 如果环境变量不存在,会抛出 KeyError
database_url = os.environ['DATABASE_URL']

2.2 什么是 .env 文件?

.env 文件 是一个文本文件,用于存储环境变量。通常放在项目根目录。

简单理解

  • .env 文件是一个配置文件
  • 每行一个环境变量,格式:KEY=value
  • # 开头的是注释

示例 .env 文件

# 这是注释
API_KEY=sk-1234567890abcdef
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
DEBUG=True
PORT=8000

2.3 Python 基础

你需要了解:

  • 基本的 Python 语法
  • os 模块的使用(os.getenv()os.environ
  • 文件操作(读取文件)

3. 安装

3.1 安装 python-dotenv

使用 pip 安装:

# 使用 pip 安装
pip install python-dotenv

说明

  • python-dotenv 是第三方库,需要单独安装
  • 使用 pip install 命令安装

3.2 验证安装

安装后可以验证是否安装成功:

# 导入 dotenv 模块
import dotenv

# 打印版本号
# __version__ 是模块的版本属性
print(f"python-dotenv 版本:{dotenv.__version__}")

# 主程序入口
if __name__ == "__main__":
print("python-dotenv 安装成功!")

说明

  • dotenv.__version__ 显示安装的版本
  • 如果导入成功,说明安装正确

4. .env 文件格式

4.1 基本语法

.env 文件使用简单的键值对格式:

# .env 文件示例
# 这是注释,以 # 开头

# 基本格式:KEY=value
API_KEY=sk-1234567890abcdef

# 带空格的值需要用引号
APP_NAME="My Application"

# 数字值(仍然是字符串)
PORT=8000

# 布尔值(仍然是字符串)
DEBUG=True

说明

  • 每行一个环境变量
  • 格式:KEY=value(等号两边可以有空格)
  • # 开头的是注释
  • 值中的空格需要用引号包裹

4.2 实际项目示例

# .env 文件示例
# 数据库配置
DATABASE_URL=postgresql://user:password@localhost:5432/mydb

# API 密钥
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
STRIPE_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxxxxxxxxx

# 应用配置
DEBUG=True
SECRET_KEY=your-secret-key-here
ALLOWED_HOSTS=localhost,127.0.0.1,example.com

# 服务配置
EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=587
EMAIL_USE_TLS=True

说明

  • 不同类型的配置可以分组(用注释分隔)
  • 敏感信息(如密码、密钥)存储在 .env 文件中
  • 不要将 .env 文件提交到版本控制系统

5. 基本使用

5.1 最简单的用法

# 导入必要的模块
import os
from dotenv import load_dotenv

# 加载 .env 文件
# load_dotenv() 会自动查找当前目录下的 .env 文件
load_dotenv()

# 获取环境变量
# os.getenv() 从 os.environ 中获取环境变量的值
api_key = os.getenv('API_KEY')
database_url = os.getenv('DATABASE_URL')

# 打印环境变量
print(f"API Key: {api_key}")
print(f"Database URL: {database_url}")

# 主程序入口
if __name__ == "__main__":
print("环境变量加载成功!")

说明

  • load_dotenv().env 文件加载环境变量到 os.environ
  • os.getenv() 获取环境变量的值
  • 如果环境变量不存在,os.getenv() 返回 None

5.2 指定 .env 文件路径

可以指定 .env 文件的具体路径:

# 导入必要的模块
from dotenv import load_dotenv
from pathlib import Path

# 方法 1:指定相对路径
# 加载当前目录下的 .env.local 文件
load_dotenv('.env.local')

# 方法 2:指定绝对路径
# 加载指定路径的 .env 文件
load_dotenv('/absolute/path/to/.env')

# 方法 3:使用 Path 对象
# Path 对象可以更方便地处理路径
env_path = Path('.') / '.env'
load_dotenv(dotenv_path=env_path)

# 主程序入口
if __name__ == "__main__":
print("从指定路径加载 .env 文件")

说明

  • 可以指定 .env 文件的具体路径
  • 使用 Path 对象可以更方便地处理路径
  • 如果文件不存在,load_dotenv() 不会报错(静默失败)

5.3 配置选项

load_dotenv() 函数支持多个配置选项:

# 导入必要的模块
from dotenv import load_dotenv

# 加载 .env 文件,使用配置选项
load_dotenv(
dotenv_path='.env', # .env 文件路径(默认是 .env)
verbose=True, # 显示加载信息(默认是 False)
override=False, # 是否覆盖已存在的环境变量(默认是 False)
interpolate=True, # 是否进行变量插值(默认是 True)
encoding='utf-8' # 文件编码(默认是 utf-8)
)

# 主程序入口
if __name__ == "__main__":
print("使用配置选项加载 .env 文件")

说明

  • dotenv_path:指定 .env 文件路径
  • verbose:是否显示加载信息
  • override:是否覆盖已存在的环境变量
  • interpolate:是否进行变量插值(如 ${VAR}
  • encoding:文件编码

6. 常用功能

6.1 获取环境变量(带默认值)

# 导入必要的模块
import os
from dotenv import load_dotenv

# 加载 .env 文件
load_dotenv()

# 获取环境变量(带默认值)
# os.getenv() 的第二个参数是默认值
# 如果环境变量不存在,返回默认值
port = os.getenv('PORT', '8000')
debug = os.getenv('DEBUG', 'False')
api_key = os.getenv('API_KEY', 'default-key')

# 打印结果
print(f"Port: {port}")
print(f"Debug: {debug}")
print(f"API Key: {api_key}")

# 主程序入口
if __name__ == "__main__":
print("获取环境变量(带默认值)")

说明

  • os.getenv(key, default) 可以指定默认值
  • 如果环境变量不存在,返回默认值
  • 这样可以避免 None 值导致的错误

6.2 类型转换

环境变量都是字符串类型,需要时可以进行类型转换:

# 导入必要的模块
import os
from dotenv import load_dotenv

# 加载 .env 文件
load_dotenv()

# 字符串类型(默认)
# 环境变量都是字符串类型
debug_str = os.getenv('DEBUG', 'False')
print(f"Debug (字符串): {debug_str}, 类型: {type(debug_str).__name__}")

# 转换为布尔值
# 将字符串转换为布尔值
debug_bool = os.getenv('DEBUG', 'False').lower() == 'true'
print(f"Debug (布尔值): {debug_bool}, 类型: {type(debug_bool).__name__}")

# 转换为整数
# 使用 int() 将字符串转换为整数
port = int(os.getenv('PORT', '8000'))
print(f"Port: {port}, 类型: {type(port).__name__}")

# 转换为列表
# 使用 split() 将逗号分隔的字符串转换为列表
hosts_str = os.getenv('ALLOWED_HOSTS', 'localhost,127.0.0.1')
hosts = [item.strip() for item in hosts_str.split(',') if item.strip()]
print(f"Allowed Hosts: {hosts}, 类型: {type(hosts).__name__}")

# 主程序入口
if __name__ == "__main__":
print("类型转换示例")

说明

  • 环境变量都是字符串类型
  • 需要时可以使用 int()bool() 等函数转换
  • 列表可以使用 split() 方法分割

6.3 辅助函数

可以创建辅助函数来简化类型转换:

# 导入必要的模块
import os
from dotenv import load_dotenv

# 加载 .env 文件
load_dotenv()

# 定义辅助函数:获取布尔值环境变量
def get_env_bool(key, default=False):
"""获取布尔值环境变量"""
# 获取环境变量的值(字符串)
value = os.getenv(key, str(default))
# 转换为小写并检查是否为 'true'、'1' 或 'yes'
return value.lower() in ('true', '1', 'yes')

# 定义辅助函数:获取列表环境变量
def get_env_list(key, default=''):
"""获取列表环境变量"""
# 获取环境变量的值
value = os.getenv(key, default)
# 按逗号分割,去除空白,过滤空字符串
return [item.strip() for item in value.split(',') if item.strip()]

# 定义辅助函数:获取整数环境变量
def get_env_int(key, default=0):
"""获取整数环境变量"""
# 获取环境变量的值
value = os.getenv(key, str(default))
# 转换为整数
try:
return int(value)
except ValueError:
# 如果转换失败,返回默认值
return default

# 使用辅助函数
debug = get_env_bool('DEBUG', False)
port = get_env_int('PORT', 8000)
hosts = get_env_list('ALLOWED_HOSTS', 'localhost')

# 打印结果
print(f"Debug: {debug}, 类型: {type(debug).__name__}")
print(f"Port: {port}, 类型: {type(port).__name__}")
print(f"Hosts: {hosts}, 类型: {type(hosts).__name__}")

# 主程序入口
if __name__ == "__main__":
print("使用辅助函数获取环境变量")

说明

  • 辅助函数可以简化类型转换
  • get_env_bool() 将字符串转换为布尔值
  • get_env_list() 将逗号分隔的字符串转换为列表
  • get_env_int() 将字符串转换为整数

6.4 直接获取所有变量(不修改 os.environ)

dotenv_values() 可以直接获取 .env 文件中的所有变量,而不修改 os.environ

# 导入必要的模块
from dotenv import dotenv_values

# 直接获取 .env 文件中的所有变量
# dotenv_values() 返回一个字典,包含所有环境变量
# 不会修改 os.environ
config = dotenv_values('.env')

# 打印所有配置
print("所有环境变量:")
for key, value in config.items():
print(f" {key} = {value}")

# 访问特定变量
api_key = config.get('API_KEY')
database_url = config.get('DATABASE_URL')

print(f"\nAPI Key: {api_key}")
print(f"Database URL: {database_url}")

# 主程序入口
if __name__ == "__main__":
print("直接获取所有环境变量")

说明

  • dotenv_values() 返回字典,包含所有环境变量
  • 不会修改 os.environ
  • 适合只需要读取配置,不需要修改系统环境变量的场景

7. 实际应用示例

7.1 简单的配置管理

创建一个简单的配置管理类:

# 导入必要的模块
import os
from dotenv import load_dotenv

# 加载 .env 文件
load_dotenv()

# 定义配置类
class Config:
"""应用配置类"""

# 从环境变量获取配置
# SECRET_KEY 是应用密钥(用于加密等)
SECRET_KEY = os.getenv('SECRET_KEY', 'default-secret-key')

# DEBUG 模式(开发时启用)
DEBUG = os.getenv('DEBUG', 'False').lower() == 'true'

# 数据库 URL
DATABASE_URL = os.getenv('DATABASE_URL', 'sqlite:///db.sqlite3')

# API 密钥
API_KEY = os.getenv('API_KEY')

# 端口号(转换为整数)
PORT = int(os.getenv('PORT', '8000'))

# 允许的主机(转换为列表)
ALLOWED_HOSTS = os.getenv('ALLOWED_HOSTS', 'localhost').split(',')

# 创建配置实例
config = Config()

# 打印配置信息
print("应用配置:")
print(f" Secret Key: {config.SECRET_KEY[:20]}...")
print(f" Debug: {config.DEBUG}")
print(f" Database URL: {config.DATABASE_URL}")
print(f" API Key: {config.API_KEY}")
print(f" Port: {config.PORT}")
print(f" Allowed Hosts: {config.ALLOWED_HOSTS}")

# 主程序入口
if __name__ == "__main__":
print("配置管理示例")

说明

  • 使用类来组织配置,更清晰
  • 所有配置都从环境变量读取
  • 可以设置默认值,避免配置缺失

7.2 配置验证

在应用启动时验证必要的配置是否存在:

# 导入必要的模块
import os
from dotenv import load_dotenv

# 加载 .env 文件
load_dotenv()

# 定义配置验证器
class ConfigValidator:
"""配置验证器:检查必要的配置是否存在"""

# 定义必要的配置项
REQUIRED_KEYS = [
'SECRET_KEY',
'DATABASE_URL',
'API_KEY'
]

@classmethod
def validate(cls):
"""验证配置"""
# 存储缺失的配置项
missing = []

# 检查每个必要的配置项
for key in cls.REQUIRED_KEYS:
# 如果配置项不存在或为空,添加到缺失列表
if not os.getenv(key):
missing.append(key)

# 如果有缺失的配置项,抛出异常
if missing:
raise ValueError(f"缺少必要的环境变量: {', '.join(missing)}")

# 所有配置都存在
print("✓ 所有必要配置已设置")

# 主程序入口
if __name__ == "__main__":
# 在应用启动时验证配置
try:
ConfigValidator.validate()
print("应用可以正常启动")
except ValueError as e:
print(f"配置错误:{e}")
print("请检查 .env 文件,确保所有必要的配置都已设置")

说明

  • 配置验证器检查必要的配置是否存在
  • 如果缺少配置,抛出异常,避免应用启动后出错
  • 在应用启动时调用验证器

8. 最佳实践

8.1 项目结构

推荐的项目结构:

my_project/
├── .env.example # 示例配置(提交到版本控制)
├── .env # 实际配置(不提交到版本控制)
├── .gitignore # 包含 .env
├── config.py # 配置加载代码
└── main.py # 主程序

说明

  • .env.example 是示例文件,包含配置项但不包含真实值
  • .env 是实际配置文件,包含真实值,不提交到版本控制
  • .gitignore 中应该包含 .env

8.2 创建 .env.example

创建 .env.example 文件作为模板:

# .env.example
# 复制此文件为 .env 并填写实际值

# 数据库配置
DATABASE_URL=postgresql://user:password@localhost:5432/dbname

# 安全密钥
SECRET_KEY=your-secret-key-here

# API 密钥
API_KEY=your-api-key-here

# 应用设置
DEBUG=True
PORT=8000
ALLOWED_HOSTS=localhost,127.0.0.1

说明

  • .env.example 是模板文件,不包含敏感信息
  • 新开发者可以复制 .env.example.env 并填写实际值
  • .env.example 可以提交到版本控制

8.3 .gitignore 配置

.gitignore 中添加 .env 文件:

# .gitignore
# 忽略 .env 文件(包含敏感信息)
.env

# 忽略所有 .env 相关文件
.env.local
.env.*.local

说明

  • .env 文件包含敏感信息,不应该提交到版本控制
  • .gitignore 中添加 .env,确保不会被提交

9. 常见问题

9.1 .env 文件未找到

如果 .env 文件不在当前目录,需要指定路径:

# 导入必要的模块
from dotenv import load_dotenv
from pathlib import Path

# 确保路径正确
# Path(__file__).parent 获取当前文件所在目录
env_path = Path(__file__).parent / '.env'

# 检查文件是否存在
if env_path.exists():
# 文件存在,加载环境变量
load_dotenv(env_path)
print(f"✓ 已加载 {env_path}")
else:
# 文件不存在,显示警告
print(f"警告: 未找到 {env_path}")

# 主程序入口
if __name__ == "__main__":
print("检查 .env 文件是否存在")

说明

  • 使用 Path(__file__).parent 获取当前文件所在目录
  • 检查文件是否存在,避免静默失败
  • 如果文件不存在,显示警告信息

9.2 环境变量未加载

如果环境变量未加载,可以检查:

# 导入必要的模块
import os
from dotenv import load_dotenv

# 使用 override=True 强制覆盖
# override=True 会覆盖已存在的环境变量
load_dotenv(override=True)

# 检查是否加载成功
# 尝试获取一个环境变量
my_key = os.environ.get('MY_KEY')

if my_key:
print(f"✓ 环境变量已加载: MY_KEY = {my_key}")
else:
print("✗ 环境变量未加载,请检查 .env 文件")

# 打印所有环境变量(仅用于调试)
# print("所有环境变量:")
# for key, value in os.environ.items():
# if key.startswith('MY_'):
# print(f" {key} = {value}")

# 主程序入口
if __name__ == "__main__":
print("检查环境变量是否加载")

说明

  • 使用 override=True 强制覆盖已存在的环境变量
  • 检查环境变量是否存在
  • 可以打印所有环境变量用于调试

9.3 环境变量值为 None

如果环境变量不存在,os.getenv() 返回 None,需要处理:

# 导入必要的模块
import os
from dotenv import load_dotenv

# 加载 .env 文件
load_dotenv()

# 方法 1:使用默认值
# 如果环境变量不存在,使用默认值
api_key = os.getenv('API_KEY', 'default-key')
print(f"API Key: {api_key}")

# 方法 2:检查是否为 None
# 如果环境变量不存在,进行特殊处理
database_url = os.getenv('DATABASE_URL')
if database_url is None:
print("警告: DATABASE_URL 未设置,使用默认值")
database_url = 'sqlite:///db.sqlite3'
print(f"Database URL: {database_url}")

# 方法 3:使用 os.environ.get()(与方法 1 相同)
# os.environ.get() 也可以指定默认值
secret_key = os.environ.get('SECRET_KEY', 'default-secret-key')
print(f"Secret Key: {secret_key}")

# 主程序入口
if __name__ == "__main__":
print("处理环境变量为 None 的情况")

说明

  • 使用默认值避免 None
  • 检查是否为 None 并进行特殊处理
  • os.environ.get() 也可以指定默认值

10. 总结

10.1 核心概念回顾

  • python-dotenv :从 .env 文件加载环境变量的工具
  • .env 文件 :存储环境变量的文本文件
  • load_dotenv() :加载 .env 文件中的环境变量
  • os.getenv() :获取环境变量的值

10.2 基本使用流程

  1. 安装:pip install python-dotenv
  2. 创建 .env 文件:在项目根目录创建 .env 文件
  3. 加载环境变量:load_dotenv()
  4. 使用环境变量:os.getenv('KEY')

10.3 使用 python-dotenv 的好处

  1. 安全 :敏感信息不写在代码中
  2. 灵活 :不同环境可以使用不同的配置
  3. 简单 :一行代码就能加载所有配置
  4. 版本控制友好.env 文件不提交,保护敏感信息