Москва: проверенные б/у ноутбуки с гарантией и проверкой перед оплатой
+7 906 722-94-45

Главная → Статьи → Виртуальные окружения Python: изоляция зависимостей

Виртуальные окружения Python: как работает изоляция и где она ломается

Виртуальные окружения Python: механизм изоляции через sys.path и site-packages, конфликты глобальных пакетов, окружение в контейнере и типичные ошибки.

Прямой ответ

Виртуальное окружение изолирует зависимости проекта: пакеты устанавливаются в отдельный каталог, а не в системное расположение. Без него установка пакета для одного проекта меняет версию, используемую всеми остальными.

Механизм изоляции основан на пути поиска модулей. Интерпретатор ищет модули в каталогах из sys.path, куда входит каталог site-packages:

$ python -m venv .venv
$ source .venv/bin/activate
$ python -c "import sys; print(sys.prefix)"
/home/user/project/.venv
$ python -c "import site; print(site.getsitepackages())"
['/home/user/project/.venv/lib/python3.12/site-packages']

Активация изменяет переменную PATH, поэтому команда python запускает интерпретатор из каталога окружения, а он использует свой site-packages. Системные пакеты при этом остаются видимыми, если окружение создано без полной изоляции.

Конфликт без окружения выглядит так:

Проект A требует requests 2.28
Проект B требует requests 2.32
Без изоляции: установлена одна версия, второй проект работает неверно

Установка в глобальное окружение также требует прав на запись в системный каталог, поэтому в современных дистрибутивах она блокируется без явного флага.

Отдельный вопрос — окружение внутри контейнера. Распространённая практика — устанавливать пакеты прямо в системный Python образа, поскольку контейнер уже изолирован. Это работает, но имеет следствие: базовый образ содержит системные пакеты, которые могут конфликтовать с устанавливаемыми.

FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

Здесь виртуальное окружение не создаётся намеренно: контейнер одноразовый, и конфликт с другим проектом невозможен. Ограничение — обновление системного пакета в образе может сломать установленные зависимости; о сборке — в статье про многоэтапные образы.

Где изоляция не защищает

Виртуальное окружение изолирует пакеты Python, но не все уровни зависимостей.

Системные библиотеки. Пакет, использующий системную библиотеку через интерфейс внешних функций, зависит от версии, установленной в системе. Окружение не управляет системными библиотеками, поэтому расхождение версий между машинами сохраняется.

Версия интерпретатора. Окружение привязано к конкретной версии Python. Проект, работающий на одной версии, может вести себя иначе на другой, и окружение это не проверяет.

Инструменты сборки. Компилятор и заголовочные файлы берутся из системы, поэтому сборка пакета с расширением зависит от окружения машины, а не от виртуального окружения.

Переменные окружения. Конфигурация через переменные не изолируется. Два проекта с разными значениями одной переменной влияют друг на друга при запуске из одной оболочки.

Полная изоляция достигается контейнером: он фиксирует версию системных библиотек, интерпретатора и инструментов сборки. Виртуальное окружение решает задачу изоляции пакетов Python, а контейнер — задачу воспроизводимости всей среды выполнения.

Практический вывод: для разработки достаточно виртуального окружения, а для проверки воспроизводимости установки и сборки требуется чистый контейнер.

Практика: использование окружений без типичных ошибок

1. Создавайте окружение для каждого проекта. Одно окружение на несколько проектов возвращает проблему конфликтов, от которой изоляция защищает.

2. Проверяйте активное окружение перед установкой. Команда which python показывает, какой интерпретатор будет использован. Установка без активации попадает в системное расположение.

3. Не устанавливайте пакеты глобально без необходимости. Глобальная установка меняет версии для всех проектов и требует прав на запись в системный каталог.

4. Используйте полную изоляцию при воспроизведении. Флаг отключения системных пакетов создаёт окружение, содержащее только установленные явно зависимости.

5. Не храните окружение в репозитории. Каталог окружения содержит абсолютные пути и не переносится между машинами; о компоновке — в статье про структуру проекта.

6. Пересоздавайте окружение при смене версии Python. Окружение привязано к конкретной версии интерпретатора, и обновление системы может сделать его нерабочим.

7. Различайте инструменты управления. Встроенный модуль создаёт окружение, а инструменты вроде uv дополнительно управляют зависимостями; о выборе — в статье про venv, conda и uv.

8. Фиксируйте зависимости отдельно от окружения. Окружение воспроизводимо только при наличии перечня зависимостей с версиями.

Рекомендации:

  1. Проверяйте путь интерпретатора в тестах. Тест, запущенный не из того окружения, использует другие версии зависимостей.
  2. Удаляйте и пересоздавайте окружение при странных ошибках. Накопленные пакеты от прежних экспериментов — частая причина необъяснимого поведения.
  3. Используйте отдельное окружение для инструментов разработки. Линтер и форматировщик, установленные в окружение проекта, могут конфликтовать с его зависимостями.
  4. Не смешивайте установку через менеджер пакетов системы и pip. Пакет, установленный двумя способами, существует в двух расположениях с разными версиями.
  5. Проверяйте установку в чистом окружении. Только так выявляется зависимость от пакетов, присутствующих в рабочем окружении по историческим причинам.

Практический сценарий: расхождение поведения между машиной разработчика и конвейером

Контекст: тесты проходили локально и падали в конвейере с ошибкой импорта. Код не менялся между запусками.

  1. Диагностика. В локальном окружении присутствовал пакет, установленный два года назад для другого проекта и не объявленный в зависимостях. Код использовал его неявно через импорт.
  2. Причина. Окружение разработчика накапливало пакеты годами и не пересоздавалось. Перечень зависимостей не обновлялся, поскольку импорт работал.
  3. Действия. (а) Пропущенную зависимость добавили в перечень; (б) локальное окружение пересоздали с нуля и убедились, что установка выполняется только из перечня; (в) в конвейер добавили шаг установки в чистое окружение перед прогоном тестов; (г) проверили остальные импорты на соответствие объявленным зависимостям и нашли ещё два случая; (д) добавили правило: окружение пересоздаётся при каждом изменении перечня зависимостей.
  4. Результат. Расхождение исчезло, а установка в чистом окружении выявляет неявные зависимости до попадания в конвейер.

Вывод: долгоживущее окружение накапливает пакеты и скрывает отсутствие зависимостей в перечне. Чистое окружение, созданное только из объявленных зависимостей, — единственный способ убедиться, что проект устанавливается у другого пользователя.

FAQ

Нужно ли окружение внутри контейнера? Обычно нет: контейнер изолирует проект. Окружение внутри оправдано, если в образе используются системные пакеты Python.

Почему установка требует прав администратора? Она идёт в системный каталог. В активированном окружении установка выполняется в каталог проекта без повышенных прав.

Можно ли перенести окружение на другую машину? Нет. Окружение содержит абсолютные пути. Переносится перечень зависимостей, по которому окружение создаётся заново.

Что делать, если окружение перестало работать после обновления системы? Пересоздать его. Окружение привязано к конкретной версии интерпретатора и не переживает её замену.

Источники: Python docs (venv, sys.path), PEP 405 — ссылки в шапке.