- PVSM.RU - https://www.pvsm.ru -

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

Вы когда-нибудь задумывались о том, как работают библиотеки виртуального окружения в 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). Так же мы можем заметить, что родительская папка содержит символические ссылки [1] на файлы стандартной библиотеки питона. Мы не можем создать символическую ссылку на исполняемый файл, т.к. интерпретатор всё равно разименует её до фактического пути.

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

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 [2], который вводит новый механизм для легковесных окружений.

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

Эту фичи активно использует стандартный модуль venv [3], который появился в 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.: Я являюсь автором этой статьи, можете задавать любые вопросы.

Автор: Artem Golubin

Источник [4]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/python/287727

Ссылки в тексте:

[1] символические ссылки: https://ru.wikipedia.org/wiki/%D0%A1%D0%B8%D0%BC%D0%B2%D0%BE%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B0%D1%8F_%D1%81%D1%81%D1%8B%D0%BB%D0%BA%D0%B0

[2] PEP 405: https://www.python.org/dev/peps/pep-0405/

[3] venv: https://docs.python.org/3/library/venv.html

[4] Источник: https://habr.com/post/418579/?utm_source=habrahabr&utm_medium=rss&utm_campaign=418579