Признаюсь честно - язык Python я очень люблю и всячески радуюсь, когда что-то на нём пишу. Это не единственный язык, на котором мне достаточно приятно писать, но он был первым, в который я погрузился с головой и которым пользуюсь до сих пор. Да, гуру я точно не являюсь, но тем не менее.
Рассказать же хочу про одну очень полезную утилиту (на деле - набор скриптов), которая упрощает работу с разными версиями интерпретатора и виртуальными окружениями для Python (virtualenv). Желание тестировать свой код на разных версиях Python понятно - прогресс не стоит на месте и хочется за ним поспевать. Virtualenv вполне себе самодостаточен, однако он ограничен той версии Python, с которой вы работаете.
Pyenv - форк менеджера версий для Ruby под названием Rbenv (тоже пользовался). C ruby ситуация примерно такая же, как и с python - в разных системах по умолчанию стоят разные версии интерпретаторов, и не факт, что можно будет установить что-то поновее или вполне определённой версии. Тогда кому-то пришла в голову идея сделать небольшой набор утилит, который позволит упростит следующие действия: 1) выбор нужной версии интерпретатора и скачивание его исходников с официального источника; 2) сборка и установка нужной версии в юзерспейс; 3) управление установленными версиями и возможность быстро переключаться между использованием той или иной версией интерпретатора. Думаю, примерно суть ясна, так что продолжу повествование дальше.
Пользу pyenv можно прочувствовать и на данном этапе, но также хочу упомянуть плагин к нему под названием pyenv-virtualenv. Я думаю, что название говорит само за себя: по сути, это возможность создавать виртуальные окружения, основанные на любой из имеющихся установленных версий, и управление ими точно так же, как и отдельными версиями интерпретатора (другими словами, это wrapper над virtualenv в каждой установленной версии интерпретатора).
И это действительно удобно! В системе установлен python-2.6, при этом в официальном репозитории нет версии поновее? Не беда - командой pyenv install <version>
ставим нужную версию. Нужно сделать виртуальное окружение, основанное на определённой установленной версии? pyenv virtualenv <version> <virtualenv_name>
. Нужно, чтобы в консоли, когда мы заходим в папку с проектом, становилась активной определённая версия интерпретатора - и снова pyenv local <version/virtualenv_name>
(при этом в текущей директории создастся файл .python-version
, в котором будет указана необходимая версия, а настроенное окружение будет автоматически "подхватывать" этот фалй и понимать, какую версию и какие пути окружения использовать).
В основном программистам, которые имеют дело с python, не приходится устанавливать какие-то пакеты, которым непременно нужны права root. Так как pyenv устанавливается в пространстве пользователя, ему такие права в большинстве своём совсем не нужны. Можно, конечно, ставить пакеты с опцией --user
, но всё же всегда проще просто без sudo писать pip install <package>
. И pyenv такую возможность предоставляет (помните же, что он ставится в пространство пользователя?).
Если прочитанное выше заставило задуматься об использовании этого проекта, то в настоящее время от его разработчика есть установщик, который поставит сам pyenv и его плагины - pyenv-installer. Это вполне себе универсальный способ, но пользователи Mac OS X могут воспользоваться Homebrew и поставить его оттуда. Правда, в данном случае установкой дело не должно ограничиться, так как всё ещё необходимо внести небольшие правки в переменные окружения. В базовом случае нужно сделать следующее (считаем, что установлен pyenv + pyenv-virtualenv и мы пользуемся командной оболочкой bash):
mkdir -p ~/.pyenv
echo "export PYENV_ROOT=\$HOME/.pyenv" >> ~/.bash_profile
echo "export PATH=\${PYENV_ROOT}/bin:\$PATH" >> ~/.bash_profile
echo $(pyenv init - $SHELL) >> ~/.bash_profile
echo $(pyenv virtualenv-init - $SHELL) >> ~/.bash_profile
В случае с установкой из скрипта pyenv-installer вы обязательно увидите этот шаг после отработки скрипта. Кстати, небольшой хинт: если хотите иметь путь установки pyenv отличный от $HOME/.pyenv
, задайте переменную PYENV_ROOT
перед установкой.
Разумеется, могут быть нюансы в зависимости от того, кто какой командной оболочкой привык пользоваться, - bash, zsh или fish - у них используются разные файлы профиля, могут различаться способы задания переменных окружения, другие нюансы... Могу только сказать, что перечисленными оболочками я пользовался, и везде у меня работало. Работало даже тогда, когда делал свои собственные доработки в окружении, потому что достаточно прозрачно работает конкретно это решение. Плюс ко всему, есть любимые многими фреймворки, которые упрощают настройку оболочки - bash-it, oh-my-zsh и oh-my-fish; все они имеют в своём арсенале свои внутренние "пакеты", включив которые автоматически делаются действия, описанные мной выше по настройке pyenv.
Понимаю, что прочитав до этого момента всё ещё нет понимания, как это всё работает, так что постараюсь привнести это понимание. Переменная PYENV_ROOT
определяет, где будет находиться инсталляция pyenv и его рабочие файлы. Путь $PYENV_ROOT/bin
мы включили в PATH
для того, чтобы можно было использовать установленные утилиты. Магическая команда pyenv init - $SHELL
(в случае bash) возвращает следующее:
export PATH="/home/drago/.pyenv/shims:${PATH}"
export PYENV_SHELL=bash
source '/home/drago/.pyenv/libexec/../completions/pyenv.bash'
command pyenv rehash 2>/dev/null
pyenv() {
local command
command="$1"
if [ "$#" -gt 0 ]; then
shift
fi
case "$command" in
activate|deactivate|rehash|shell)
eval "$(pyenv "sh-$command" "$@")";;
*)
command pyenv "$command" "$@";;
esac
}
Мы тут видим следующие моменты: в PATH добавляется ещё один путь - /home/drago/.pyenv/shims
, или, чтобы было понятнее, ${PYENV_ROOT}/shims
. О том, что это за папка и зачем она нужна, я расскажу чуть позже. Далее объявляется переменная окружения PYENV_SHELL
, для внутренних нужд. Применяется файл дополнений для командной оболочки, который у меня лежит в ${PYENV_ROOT}/libexec/../completions/pyenv.bash
, выполняется команда command pyenv rehash 2>/dev/null
(она связана с папкой shims, так что тоже чуть позже), затем объявлена небольшая функция, которая позволяет пользоваться предоставляемыми командами через единую точку входа pyenv.
Теперь посмотрим, что возвращает команда pyenv-virtualenv init - $SHELL
:
export PATH="/home/drago/.pyenv/plugins/pyenv-virtualenv/shims:${PATH}";
export PYENV_VIRTUALENV_INIT=1;
_pyenv_virtualenv_hook() {
local ret=$?
if [ -n "$VIRTUAL_ENV" ]; then
eval "$(pyenv sh-activate --quiet || pyenv sh-deactivate --quiet || true)" || true
else
eval "$(pyenv sh-activate --quiet || true)" || true
fi
return $ret
};
if ! [[ "$PROMPT_COMMAND" =~ _pyenv_virtualenv_hook ]]; then
PROMPT_COMMAND="_pyenv_virtualenv_hook;$PROMPT_COMMAND";
fi
Сначала добавляется ещё одна папка shims, уже в папке с pyenv-virtualenv. Затем объявляется переменная для внутренних нужд PYENV_VIRTUALENV_INIT=1
, затем простенький хук для командной оболочки, который автоматически при необходимости применять ту или иную версию интерпретатора или виртуального окружения.
Установка pyenv в целом сводится к тому, что стягиваются все необходимые файлы (всё скрипты), но как же всё-таки это работает?
- В самом начале была выполнена команда
command pyenv rehash 2>/dev/null
. Эта команда, по сути, наполняет директориюshims
, которую мы не раз упоминали, легковесными командами-роутерами (как я их для себя очертил), которые исходя из текущего окружения понимают, по какому конкретно пути нужно дёргать ту или иную команду. - Хук
_pyenv_virtualenv_hook()
отрабатывает каждый раз при использовании командной оболочки, и понимает, какая конкретно версия используется в данный момент (факторов может быть много: это может быть переменная окруженияPYENV_VERSION
, переменная окруженияVIRTUAL_ENV
, наличие в локальной директории файла.python-version
, в котором прописана версия, которую нужно использовать, или же использовать системную версию интерпретатора python в случае, если не использован ни один из способов задания текущей рабочей версии. - Версии интерпретаторов, установленных pyenv'ом, располагаются по пути
${PYENV_ROOT}/versions
, в случае виртуального окружения это будет симлинк на папку внутри определённой версии -${PYENV_ROOT}/versions/<version>/envs/<env_name>
. - Исходя из всего вышеописанного, когда мы выполняем любую команду из shims, происходит "разыменование" нужного пути, по которому использовать команду, и передаётся управление ей. Кавычки тут не зря, так как это не разыменование ссылок, как можно было бы подумать.
Вот пример одной из таких легковесных "команд-роутеров", расположена по пути ${PYENV_ROOT}/shims/pip
:
#!/usr/bin/env bash
set -e
[ -n "$PYENV_DEBUG" ] && set -x
program="${0##*/}"
if [[ "$program" = "python"* ]]; then
for arg; do
case "$arg" in
-c* | -- ) break ;;
*/* )
if [ -f "$arg" ]; then
export PYENV_FILE_ARG="$arg"
break
fi
;;
esac
done
fi
export PYENV_ROOT="/home/drago/.pyenv"
exec "/home/drago/.pyenv/libexec/pyenv" exec "$program" "$@"
Надеюсь, статья будет кому-то полезной. =) Happy coding!