30.07.2018       Выпуск 241 (30.07.2018 - 05.08.2018)       Статьи

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

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

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

Читать>>




Экспериментальная функция:

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

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

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



Лучшая Python рассылка




Разместим вашу рекламу

Пиши: mail@pythondigest.ru

Нашли опечатку?

Выделите фрагмент и отправьте нажатием Ctrl+Enter.

Система Orphus