Переменные окружения в Python: os.environ и dotenv (практическое руководство)

Переменные окружения в Python: os.environ и dotenv (практическое руководство)

Переменные окружения в 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 для удобной разработки, валидируйте обязательные параметры, храните секреты вне репозитория и не забывайте про безопасность в логах. Такой подход упрощает переносимость, снижает риски и помогает быстро масштабировать приложение между средами.

    Источник

    НЕТ КОММЕНТАРИЕВ

    Оставить комментарий