Как стать автором
Обновить

Как работают библиотеки виртуального окружения

Время на прочтение 5 мин
Количество просмотров 9.9K
Автор оригинала: Artem Golubin
Вы когда-нибудь задумывались о том, как работают библиотеки виртуального окружения в Python? В этой статье я предлагаю ознакомится с главной концепцией, которую используют все библиотеки для окружений, такие как virtualenv, virtualenvwrapper, conda, pipenv.

Изначально, в Python не было встроенной возможности создавать окружения, и такая возможность была реализована в виде хака. Как оказалось, все библиотеки базируются на очень простой особенности интерпретатора питона.

Когда Python запускает интерпретатор, он начинает искать директорию с модулями (site-packages). Поиск начинается с родительской директории относительно физического расположения исполняемого файла интерпретатора (python.exe). Если папка с модулями не найдена, то Python переходит на уровень выше, и делает это до тех пор, пока не будет достигнута корневая директория. Для того, чтобы понять, что это директория с модулями, Python ищет модуль os, который должен лежать в файле os.py и является обязательным для работы питона.

Давайте представим, что наш интерпретатор располагается по адресу /usr/dev/lang/bin/python. Тогда пути поиска будут выглядеть так:

/usr/dev/lang/lib/python3.7/os.py
/usr/dev/lib/python3.7/os.py
/usr/lib/python3.7/os.py
/lib/python3.7/os.py

Как вы можете видеть, Python добавляет специальный префикс (lib/python$VERSION/os.py) к нашему пути. Как только интерпретатор находит первое совпадение (наличие файла os.py), он изменяет sys.prefix и sys.exec_prefix на этот путь (с удаленным префиксом). Если по каким-то причинам совпадений не найдено, то используется стандартный путь, который вкомпилирован в интерпретатор.

Теперь давайте посмотрим как это делает одна из самых старых и известных библиотек — virtualenv.

user@arb:/usr/home/test# virtualenv ENV
Running virtualenv with interpreter /usr/bin/python3
New python executable in /usr/home/test/ENV/bin/python3
Also creating executable in /usr/home/test/ENV/bin/python
Installing setuptools, pkg_resources, pip, wheel...done.

После выполнения, она создает дополнительные директории:

user@arb:/usr/home/test/ENV# tree -L 3
.
├── bin
│   ├── activate
│   ├── activate.csh
│   ├── activate.fish
│   ├── activate_this.py
│   ├── easy_install
│   ├── easy_install-3.7
│   ├── pip
│   ├── pip3
│   ├── pip3.7
│   ├── python
│   ├── python-config
│   ├── python3 -> python
│   ├── python3.7 -> python
│   └── wheel
├── include
│   └── python3.7m -> /usr/include/python3.7m
├── lib
│   └── python3.7
│   ├── __future__.py -> /usr/lib/python3.7/__future__.py
│   ├── __pycache__
│   ├── _bootlocale.py -> /usr/lib/python3.7/_bootlocale.py
│   ├── _collections_abc.py -> /usr/lib/python3.7/_collections_abc.py
│   ├── _dummy_thread.py -> /usr/lib/python3.7/_dummy_thread.py
│   ├── _weakrefset.py -> /usr/lib/python3.7/_weakrefset.py
│   ├── abc.py -> /usr/lib/python3.7/abc.py
│   ├── base64.py -> /usr/lib/python3.7/base64.py
│   ├── bisect.py -> /usr/lib/python3.7/bisect.py
│   ├── codecs.py -> /usr/lib/python3.7/codecs.py
│   ├── collections -> /usr/lib/python3.7/collections
│   ├── config-3.7m-darwin -> /usr/lib/python3.7/config-3.7m-darwin
│   ├── copy.py -> /usr/lib/python3.7/copy.py
│   ├── copyreg.py -> /usr/lib/python3.7/copyreg.py
│   ├── distutils
│   ├── encodings -> /usr/lib/python3.7/encodings
│   ├── enum.py -> /usr/lib/python3.7/enum.py
│   ├── fnmatch.py -> /usr/lib/python3.7/fnmatch.py
│   ├── functools.py -> /usr/lib/python3.7/functools.py
│   ├── genericpath.py -> /usr/lib/python3.7/genericpath.py
│   ├── hashlib.py -> /usr/lib/python3.7/hashlib.py
│   ├── heapq.py -> /usr/lib/python3.7/heapq.py
│   ├── hmac.py -> /usr/lib/python3.7/hmac.py
│   ├── imp.py -> /usr/lib/python3.7/imp.py
│   ├── importlib -> /usr/lib/python3.7/importlib
│   ├── io.py -> /usr/lib/python3.7/io.py
│   ├── keyword.py -> /usr/lib/python3.7/keyword.py
│   ├── lib-dynload -> /usr/lib/python3.7/lib-dynload
│   ├── linecache.py -> /usr/lib/python3.7/linecache.py
│   ├── locale.py -> /usr/lib/python3.7/locale.py
│   ├── no-global-site-packages.txt
│   ├── ntpath.py -> /usr/lib/python3.7/ntpath.py
│   ├── operator.py -> /usr/lib/python3.7/operator.py
│   ├── orig-prefix.txt
│   ├── os.py -> /usr/lib/python3.7/os.py
│   ├── posixpath.py -> /usr/lib/python3.7/posixpath.py
│   ├── random.py -> /usr/lib/python3.7/random.py
│   ├── re.py -> /usr/lib/python3.7/re.py
│   ├── readline.so -> /usr/lib/python3.7/lib-dynload/readline.cpython-37m-darwin.so
│   ├── reprlib.py -> /usr/lib/python3.7/reprlib.py
│   ├── rlcompleter.py -> /usr/lib/python3.7/rlcompleter.py
│   ├── shutil.py -> /usr/lib/python3.7/shutil.py
│   ├── site-packages
│   ├── site.py
│   ├── sre_compile.py -> /usr/lib/python3.7/sre_compile.py
│   ├── sre_constants.py -> /usr/lib/python3.7/sre_constants.py
│   ├── sre_parse.py -> /usr/lib/python3.7/sre_parse.py
│   ├── stat.py -> /usr/lib/python3.7/stat.py
│   ├── struct.py -> /usr/lib/python3.7/struct.py
│   ├── tarfile.py -> /usr/lib/python3.7/tarfile.py
│   ├── tempfile.py -> /usr/lib/python3.7/tempfile.py
│   ├── token.py -> /usr/lib/python3.7/token.py
│   ├── tokenize.py -> /usr/lib/python3.7/tokenize.py
│   ├── types.py -> /usr/lib/python3.7/types.py
│   ├── warnings.py -> /usr/lib/python3.7/warnings.py
│   └── weakref.py -> /usr/lib/python3.7/weakref.py
└── pip-selfcheck.json

Как вы можете видеть, виртуальное окружение было создано путём копирования бинарника Python в локальную папку (ENV/bin/python). Так же мы можем заметить, что родительская папка содержит символические ссылки на файлы стандартной библиотеки питона. Мы не можем создать символическую ссылку на исполняемый файл, т.к. интерпретатор всё равно разименует её до фактического пути.

Теперь давайте активируем наше окружение:

user@arb:/usr/home/test# source ENV/bin/activate

Эта команда меняет переменную окружения $PATH, таким образом, чтобы команда python указывала на нашу локальную версию питона. Это достигается путём подстановки локального пути папки bin в начало строки $PATH, чтобы локальный путь имел приоритет перед всеми путями справа.

export "/usr/home/test/ENV/bin:$PATH"
echo $PATH

Если вы запустите скрипт из этого окружения, то он выполнится с помощью бинарника по адресу /usr/home/test/ENV/bin/python. Интерпретатор будет использовать этот путь как стартовую точку для поиска модулей. В нашем случае, модули стандартной библиотеки будут найдены по пути /usr/home/test/ENV/lib/python3.7/.

Это основной хак, благодаря которому работают все библиотеки для работы с виртуальными окружениями.

Улучшения в Python 3


Начиная с версии Python 3.3, появился новый стандарт, именуемый как PEP 405, который вводит новый механизм для легковесных окружений.

Этот PEP добавляется дополнительный шаг к процессу поиска. Если создать файл конфигурации pyenv.cfg, то вместо копирования бинарника Python и всех его модулей, можно просто указать их расположение в этом конфиге.

Эту фичи активно использует стандартный модуль venv, который появился в Python 3.

user@arb:/usr/home/test2# python3 -m venv ENV
user@arb:/usr/home/test2# tree -L 3
.
└── ENV
  ├── bin
  │   ├── activate
  │   ├── activate.csh
  │   ├── activate.fish
  │   ├── easy_install
  │   ├── easy_install-3.7
  │   ├── pip
  │   ├── pip3
  │   ├── pip3.5
  │   ├── python -> python3
  │   └── python3 -> /usr/bin/python3
  ├── include
  ├── lib
  │   └── python3.7
  ├── lib64 -> lib
  ├── pyvenv.cfg
  └── share
  └── python-wheels

user@arb:/usr/home/test2# cat ENV/pyvenv.cfg
home = /usr/bin
include-system-site-packages = false
version = 3.7.0
user@arb:/usr/home/test2# readlink ENV/bin/python3
/usr/bin/python3

Благодаря этому конфигу, вместо копирования бинарника, venv просто создает ссылку на него. Если параметр include-system-site-packages изменить на true, то все модули стандартной библиотеки будут автоматически доступны из виртуального окружения.

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

P.S.: Я являюсь автором этой статьи, можете задавать любые вопросы.
Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
+22
Комментарии 2
Комментарии Комментарии 2

Публикации

Истории

Работа

Data Scientist
66 вакансий
Python разработчик
136 вакансий

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн
PG Bootcamp 2024
Дата 16 апреля
Время 09:30 – 21:00
Место
Минск Онлайн
EvaConf 2024
Дата 16 апреля
Время 11:00 – 16:00
Место
Москва Онлайн