Переменные окружения в Python: os.environ и dotenv (практическое руководство)
Переменные окружения помогают отделить конфигурацию от кода: вы не хардкодите пароли и ключи API, а читаете их из окружения. В Python для этого есть словарь os.environ, а для удобной локальной разработки — библиотека python-dotenv, читающая значения из файла .env.
Что такое переменные окружения и зачем они нужны
- Хранение секретов: токены, пароли, ключи API.
- Универсальная конфигурация: один и тот же код работает в dev, staging и prod средах без правок исходников.
- Совместимость: работает в Docker, CI/CD и на серверах без дополнительных зависимостей.
os.environ: чтение, запись, удаление
os.environ — это отображение (похоже на словарь), которое содержит переменные окружения текущего процесса.
import os
# Чтение: безопасно с умолчанием
home = os.environ.get("HOME", "")
print("HOME:", home)
# Жёсткое чтение (поднимет KeyError, если нет переменной)
# path = os.environ["PATH"]
# Установка переменной для текущего процесса (и дочерних)
os.environ["API_TOKEN"] = "secret-123"
print("API_TOKEN set:", os.environ.get("API_TOKEN"))
# Удаление переменной (если отсутствует — KeyError)
if "API_TOKEN" in os.environ:
del os.environ["API_TOKEN"]
os.putenv vs os.environ
os.putenv существует для совместимости с низкоуровневым API ОС, но в большинстве случаев достаточно работы с os.environ. Изменения в os.environ автоматически отражаются в окружении дочерних процессов.
Безопасные паттерны чтения конфигурации
Заведите небольшие утилиты для обязательных и необязательных переменных с преобразованием типов.
import os
class ConfigError(RuntimeError):
pass
def get_required(name: str) -> str:
value = os.environ.get(name)
if value is None or value == "":
raise ConfigError(f"Отсутствует обязательная переменная окружения: {name}")
return value
def get_int(name: str, default: int) -> int:
raw = os.environ.get(name)
if raw is None:
return default
try:
return int(raw)
except ValueError:
raise ConfigError(f"Переменная {name} должна быть целым числом (получено: {raw!r})")
def get_bool(name: str, default: bool) -> bool:
raw = os.environ.get(name)
if raw is None:
return default
return raw.strip().lower() in {"1", "true", "yes", "y", "on"}
.env и библиотека python-dotenv
Для локальной разработки удобно хранить значения в файле .env и подгружать их при старте приложения.
# Установка
pip install python-dotenv
Пример файла .env (не коммитьте его в репозиторий):
# .env
API_TOKEN=secret-123
DB_URL=postgresql+psycopg://user:pass@localhost:5432/app
DEBUG=true
PORT=8080
# Комментарии начинаются с #
Загрузка значений в окружение:
import os
from dotenv import load_dotenv, find_dotenv
# Ищет .env, поднимаясь по дереву каталогов, и загружает его
load_dotenv(find_dotenv())
print("API_TOKEN:", os.getenv("API_TOKEN"))
По умолчанию load_dotenv не перезаписывает уже существующие переменные. Для явного перекрытия используйте override=True.
load_dotenv(find_dotenv(), override=True)
Несколько файлов .env
- .env — базовый файл для разработки.
- .env.local — локальные переопределения (в .gitignore).
- .env.production — значения для продакшена (храните вне репозитория).
Вы можете явно загрузить нужный файл:
from dotenv import load_dotenv
load_dotenv(".env.production")
Мини‑проект: конфигурация через окружение
Соберём простой модуль конфигурации, который валидирует обязательные значения и приводит типы.
# config.py
import os
from dotenv import load_dotenv, find_dotenv
load_dotenv(find_dotenv()) # безопасно для локалки
class ConfigError(RuntimeError):
pass
def _required(name: str) -> str:
value = os.environ.get(name)
if not value:
raise ConfigError(f"Отсутствует {name}. Задайте её в окружении или .env")
return value
def _to_bool(value: str, default: bool=False) -> bool:
if value is None:
return default
return value.strip().lower() in {"1", "true", "yes", "y", "on"}
def _to_int(value: str, default: int) -> int:
if value is None:
return default
try:
return int(value)
except ValueError as e:
raise ConfigError(f"Ожидалось целое число, получено: {value!r}") from e
API_TOKEN = _required("API_TOKEN")
DB_URL = _required("DB_URL")
DEBUG = _to_bool(os.environ.get("DEBUG"), default=False)
PORT = _to_int(os.environ.get("PORT"), default=8000)
# main.py
from config import API_TOKEN, DB_URL, DEBUG, PORT
def run():
print("Config loaded:\n",
f"API_TOKEN=**** (len={len(API_TOKEN)})\n",
f"DB_URL={DB_URL}\n",
f"DEBUG={DEBUG}\n",
f"PORT={PORT}")
if __name__ == "__main__":
run()
Запуск с передачей переменных на лету:
# Linux/macOS (bash/zsh)
API_TOKEN=xxx DB_URL=sqlite:///db.sqlite3 python main.py
# Windows PowerShell
$env:API_TOKEN="xxx"; $env:DB_URL="sqlite:///db.sqlite3"; python .\main.py
# Windows CMD
set API_TOKEN=xxx && set DB_URL=sqlite:///db.sqlite3 && python main.py
Советы и типичные ошибки
- Не коммитьте .env с секретами. Добавьте .env в .gitignore и держите образец .env.example без реальных значений.
- Всегда валидируйте обязательные переменные и явно приводите типы (int, bool).
- Не используйте
os.environ["KEY"]без проверки — ловите KeyError. Предпочтительнееos.getenvили обёртки. - Для продакшена устанавливайте переменные окружения на уровне оркестратора (Docker, systemd, CI/CD), а не из .env.
- Не логируйте секреты. Если нужно отладить — логируйте только факт наличия и длину.
FAQ коротко
Как задать переменную только для одного запуска?
# Linux/macOS
DEBUG=1 python app.py
Как проверить, установлена ли переменная?
import os
if os.getenv("API_TOKEN"):
print("Токен задан")
else:
print("Токен отсутствует")
Почему моих переменных из .env не видно? Убедитесь, что вызвали load_dotenv до чтения переменных и что путь к .env корректный (используйте find_dotenv()).
Куда двигаться дальше
Хотите системно освоить Python с нуля, научиться работать с файловой системой, сетью, тестами и деплоем? Рекомендую пройти практический курс: Освоить Python с нуля и довести навыки до уровня «Гуру» — присоединиться к курсу.
Итоги
Переменные окружения — фундаментальный инструмент конфигурации в Python. Используйте os.environ и python-dotenv для удобной разработки, валидируйте обязательные параметры, храните секреты вне репозитория и не забывайте про безопасность в логах. Такой подход упрощает переносимость, снижает риски и помогает быстро масштабировать приложение между средами.
-
Создано 01.06.2026 17:01:47
-
Михаил Русаков

Комментарии (0):
Для добавления комментариев надо войти в систему.
Если Вы ещё не зарегистрированы на сайте, то сначала зарегистрируйтесь.