- 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.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
Нажмите здесь для печати.