Часть 1: История проблемы и эволюция решений
1. Эпоха хаоса (Python < 2.4)
Проблема: В начале был только один глобальный путь site-packages. Если Проект А требовал Django 1.0, а Проект Б — Django 2.0, вы были обречены. Установка новой версии ломала старый проект. Это называлось Dependency Hell.
Что за site-packages?
site-packages — это целевая папка (каталог) в Python, предназначенная для хранения сторонних библиотек и пакетов, устанавливаемых пользователем (когда вы выполняете pip install <package_name>, библиотека попадает именно в site-packages). Она входит в стандартный путь поиска модулей (sys.path), что позволяет импортировать установленные пакеты в любом скрипте.
Локация зависит от ОС:
на Windows: Lib/site-packages,
на Linux/macOS: /usr/local/lib/python3.x/dist-packages или site-packages)
Решение: Никакого. Или тупо не обновлять пакеты.
2. Появление virtualenv (2007 год, Ian Bicking)
Идея: «А что, если мы обманем интерпретатор?» До этого момента не было стандартного способа изоляции. virtualenv стал первым популярным инструментом. Как это работало:
Скрипт копировал бинарный файл
pythonв новую папку.Создавал структуру папок
lib/pythonX.X/site-packages.Главный трюк: он патчил (изменял) код самого исполняемого файла или создавал обертку, которая при старте жестко задавала переменную
sys.prefixиsys.path, игнорируя глобальные настройки.Минус: Это был сторонний инструмент, который нужно было устанавливать через
pip. Он работал сложно и медленно при создании.
3. Стандартизация: venv (Python 3.3+, PEP 405)
Прорыв: Разработчики поняли, что изоляция нужна всем, и включили механизм прямо в ядро Python.
Суть изменения: Вместо того чтобы копировать тяжелый бинарник интерпретатора (что делал virtualenv), venv создает символические ссылки (symlinks) на системный python.
В Linux/macOS:
.venv/bin/python->/usr/bin/python3.9.В Windows: создается небольшой лаунчер
.exe, который перенаправляет вызов.
Ключевой файл: pyvenv.cfg. Это текстовый файл, который появился вместе с venv. Он говорит интерпретатору: «Эй, ты сейчас запускаешься в режиме виртуального окружения. Твой домашний каталог вот здесь, и пожалуйста, не смотри в глобальные папки».
Часть 2: Как это работает «под капотом» (Архитектура)
Когда вы запускаете python внутри виртуального окружения, происходит следующая цепочка событий:
1. Запуск исполняемого файла:
Вы вызываете .venv/bin/python.
- Если это симлинк: ОС прозрачно перенаправляет вызов на реальный /usr/bin/python3.
- Если это скрипт/лаунчер: он запускает реальный интерпретатор с особыми флагами.
2. Инициализация интерпретатора (C-level):
Интерпретатор запускается и сразу же проверяет контекст.
- Он видит, что исполняемый файл лежит внутри папки, содержащей pyvenv.cfg.
- Он читает этот конфиг. Там есть строка home = /usr/bin.
3. Построение sys.path:
Это самый важный момент. Обычно Python строит путь так:
1. Директория скрипта.
2. Стандартные библиотеки (lib/python3.9).
3. Глобальные site-packages (/usr/local/lib...).
Но в режиме venv логика меняется:
1. Директория скрипта.
2. Стандартные библиотеки (берутся из home в конфиге).
3. ЛОКАЛЬНЫЕ site-packages (.venv/lib/python3.9/site-packages).
4. Глобальные site-packages ИСКЛЮЧАЮТСЯ полностью.
Именно исключение пункта 4 обеспечивает изоляцию. Интерпретатор физически не знает о существовании пакетов, установленных глобально.
4. Переменные окружения:
Скрипт активации (activate) просто меняет $PATH в вашей оболочке (bash/zsh), чтобы при вводе python попадало в .venv/bin. Но даже без активации, если вы запустите .venv/bin/python полным путем, изоляция сработает благодаря внутренней логике CPython.
Часть 3: Практика — Пишем свой виртуальный "окружитель".
Давай представим, что мы хотим реализовать упрощенный аналог venv на чистом Python. Нам нужно сделать три вещи:
1. Создать структуру папок.
2. Создать файл конфигурации pyvenv.cfg.
3. Создать скрипт-обертку (или симлинк), который заставит интерпретатор понять, что он в изоляции.
Примечание: В реальности создание исполняемых файлов и симлинков зависит от ОС, поэтому код будет эмулировать логику создания структуры и конфига, а также покажет, как интерпретатор читает этот конфиг.
Шаг 1: Структура проекта для самописного venv
my_custom_venv/ ├── pyvenv.cfg <-- Сердце изоляции ├── bin/ <-- Сюда положим ссылку на python │ └── python -> /usr/bin/python3 └── lib/ └── python3.11/ └── site-packages/ <-- Сюда будем ставить пакеты
Шаг 2: Код генератора (mini_venv.py)
Этот скрипт создаст окружение. Он демонстрирует, что именно важно для работы.
import os import sys import shutil from pathlib import Path def create_mini_venv(env_name): base_path = Path(env_name) # 1. Определяем пути текущего системного Python # sys.executable -> путь к бинарнику (например, /usr/bin/python3.11) # sys.prefix -> базовая установка (например, /usr) system_python = sys.executable system_prefix = sys.prefix # Вычисляем версию (упрощенно) version_str = f"python{sys.version_info.major}.{sys.version_info.minor}" # 2. Создаем структуру папок bin_dir = base_path / "bin" lib_dir = base_path / "lib" / version_str / "site-packages" os.makedirs(bin_dir, exist_ok=True) os.makedirs(lib_dir, exist_ok=True) print(f"📁 Создана структура папок в {base_path}") # 3. Создаем критически важный файл pyvenv.cfg # Именно наличие этого файла сигнализирует интерпретатору о режиме venv cfg_content = f""" home = {os.path.dirname(system_python)} include-system-site-packages = false version = {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro} executable = {system_python} command = {system_python} -m venv {base_path} """ (base_path / "pyvenv.cfg").write_text(cfg_content) print("⚙️ Создан pyvenv.cfg (ключ к изоляции)") # 4. Создаем "исполняемый файл" внутри venv # В Unix/Linux мы используем симлинк. В Windows нужен .exe лаунчер (здесь упрощено). venv_python = bin_dir / "python" if os.name == 'nt': # Windows # Для Windows реальная реализация сложнее (нужен exe), # но для демонстрации создадим bat-файл bat_content = f'@echo off\n"{system_python}" %*' (bin_dir / "python.bat").write_text(bat_content) print("🪟 Создан python.bat (эмуляция для Windows)") else: # Unix/Linux/Mac: создаем симлинк if venv_python.exists() or venv_python.is_symlink(): venv_python.unlink() os.symlink(system_python, venv_python) print("🐧 Создан симлинк bin/python") # 5. Создаем скрипт активации (просто меняет PATH в shell) activate_script = f""" export VIRTUAL_ENV="{base_path.resolve()}" export PATH="$VIRTUAL_ENV/bin:$PATH" unset PYTHONHOME """ (bin_dir / "activate").write_text(activate_script) print("🚀 Создан скрипт activate") print("\n✅ Готово! Чтобы проверить:") print(f" source {bin_dir}/activate") print(f" which python # должен показывать путь внутри {env_name}") print(f" python -c 'import sys; print(sys.path)' # не должно содержать глобальных site-packages") if __name__ == "__main__": if len(sys.argv) < 2: print("Использование: python mini_venv.py <имя_папки>") else: create_mini_venv(sys.argv[1])
Шаг 3: Проверка работы.
Если вы запустите этот скрипт, он создаст папку. Что произойдет, если написать командуpython в терминале?
1. Вы делаете source bin/activate. Shell меняет $PATH.
2. Вы пишете python. Shell находит my_custom_venv/bin/python.
3. Это симлинк на настоящий /usr/bin/python3.
4. Настоящий интерпретатор стартует.
5. Магия CPython: Перед выполнением любого кода, интерпретатор смотрит вверх по дереву папок от своего местоположения (bin/).
6. Он находит файл pyvenv.cfg в родительской папке.
7. Он парсит строку include-system-site-packages = false.
8. Он не добавляет глобальные пути в sys.path.
9. Он добавляет только my_custom_venv/lib/python3.X/site-packages.
Результат: Если вы попробуете import requests, а он установлен только глобально, вы получите ModuleNotFoundError. Изоляция достигнута без изменения кода самого интерпретатора, только благодаря наличию конфига и структуре папок.
Часть 4: Где здесь Poetry?
Poetry в этой схеме играет роль надстройки управления состоянием.
1. Создание: Poetry может вызвать вышеописанную логику (или нативный venv), чтобы создать папку.
2. Установка: Когда вы делаете poetry add requests:
- Poetry находит путь к интерпретатору внутри вашего venv (читая конфиг или используя sys.prefix).
- Вызывает pip install (или свой собственный инсталлер), явно указывая целевую директорию: --target /path/to/venv/lib/....
- Poetry не ставит пакеты глобально.
3. Lock: Poetry гарантирует, что версии в site-packages соответствуют poetry.lock.
Резюме
Виртуальное окружение — это не отдельная программа и не виртуальная машина. Это конвенция (соглашение) между:
Файловой системой (специфическая структура папок).
Конфигурационным файлом (
pyvenv.cfg).Логикой запуска интерпретатора Python (который запрограммирован искать этот файл и менять поведение при его наличии).
Если бы вы удалили файл pyvenv.cfg из готового виртуального окружения и запустили бы bin/python, интерпретатор перестал бы считать себя "виртуальным" и начал бы видеть глобальные пакеты, хотя физически файлы лежали бы в старой папке. Именно конфиг является переключателем режима изоляции.
