Управляем разными версиями python

Python, при всей его стабильности и устоявшемся api, продолжает активно развиваться: новые версии выходят примерно раз в два/два с половиной года. В то же время, в linux-операционных системах обновляются только минорные версии языка (скажем, 3.5.1 до 3.5.2), а новый python 3.6 из коробки можно будет "потрогать" только со следующей версией дистрибутива ОС.

Обновление ОС ради новой версии python - крайняя мера, а в случае серверов - и вовсе сомнительное удовольствие. Вот и выходит, что часто, чтобы попробовать все нововведения приходится использовать docker или устанавливать новую версию вручную. В последнем случае стоит быть очень внимательным, чтобы не заменить системную версию python, иначе можно заработать головную боль из-за проблем с совместимостью. В случае же, если альтернативную версию python необходимо установить на сервере, вариантов становится еще меньше (я обычно в таких случаях оборачиваю приложение в docker-контейнер).

Для решения этой проблемы имеется pyenv: инструмент для управления разными версиями python. С его помощью можно быстро и просто устанавливать, удалять и переключаться между множеством различных версий python, не покидая командной строки. Pyenv - это попытка объединить в одном месте все лучшее в python-мире: pip, virtualenv и pipfile. В месте с тем, это простой инструмент, который следует UNIX-традициям: делай одну вещь, но делай её хорошо.

pyenv позволяет:

  • изменять глобальную версию python, используемую по-умолчанию, для определенного пользователя;
  • выбирать определенную версию python для отдельных проектов;
  • переопределять версию python при помощи переменной окружения;
  • выполнять одни и те же команды с использованием нескольких разных версий python, удобно для проверки совместимости.

Как это работает

На уровне ОС pyenv, используя shim-исполняемые файлы добавленные в PATH-переменную, перехватывает команды, направленные к python, определяет необходимую версию python и перенаправляет команды к соответствующему исполняемому файлу.

Переменная PATH

Когда вы запускаете в консоли команду, вроде python или pip, операционная система выполняет поиск исполняемого файла с таким названием по определенному списку директорий. Этот список хранится в переменной окружения PATH, отдельные элементы в списке разделяются двоеточием:


/usr/local/bin:/usr/bin:/bin

В ходе поиска директории в списке PATH просматриваются слева направо, то есть исполняемый файл, найденный в директории в начале списка имеет преимущество перед таким же файлом в другой директории ближе к концу. В случае указанной выше PATH сначала будет выполнен поиск в /usr/local/bin, потом в /usr/bin и в самом конце - в /bin.

Использование shim

Функционал pyenv реализован путем добавления shim-директории (shim с английского "прокладка", "прослойка") в самое начало переменной PATH:


$(pyenv root)/shims:/usr/local/bin:/usr/bin:/bin

pyenv создает исполняемые файлы в shim-директории таким образом, чтобы они соответствовали реальными исполняемым Python-командам в операционной системе для всех доступных версий Python: python, python3, pip и так далее.

Эти shim-файлы представляют собой простейшие утилиты, которые просто направляют "перехваченные" команды исполняемому файлу pyenv, который уже исходя из настроек выбирает, какую версию python или pip использовать. То есть, с установленным в системе pyenv, когда вы запускаете в командной строке, скажем, pip, операционная система выполняет следующие действия:

  • выполняет поиск в PATH-директориях исполняемого файла pip
  • находит исполняемый shim-файл от pyenv с таким именем в самом начале PATH
  • исполняет найденный файл, который в свою очередь, передает команду pyenv

Выбор версии python

Когда shim-файл направляет команду pyenv, выбор версии python выполняется в такой последовательности:

  1. В первую очередь, выбирается версия из переменной окружения PYENV_VERSION (если установлена). Для установки значения в текущей сессии можно использовать команду pyenv shell.

  2. Если PYENV_VERSION не установлена, ищется файл .pyenv-version. Тами образом удобно установить Версию python, специфическую для проекта: достаточно создать соответствующий файл в текущей директории. Выполнить это можно при помощи команды pyenv local.

  3. Если в текущей директории файла .pyenv-version нет, то будет использована версия python из первого найденного .python-version файла, поиск выполняется по всем родительским директориям до достижения корня файловой системы.

  4. Ну и, если все предыдущие пункты не принесли успеха, будет использована глобальная версия, установленная в файле $(pyenv root)/version. Изменять это значение можно используя pyenv global команду. Если глобальная версия не установлена, будет использована системная версия python.

Поиск исполняемого файла выбранной версии python в файловой системе

После того, как pyenv определяет, какую версию python нужно использовать для выполнения, команда передается выбранной версии. Каждая версия Python, установленная при помощи pyenv, находится в индивидуальной директории в $(pyenv root)/versions

Например, в системе могут быть установлены следующие версии:


$(pyenv root)/versions/2.7.8/
$(pyenv root)/versions/3.4.2/
$(pyenv root)/versions/pypy-2.4.0/

С точки зрения pyenv, версии - это просто директории в $(pyenv root)/versions.

Установка

Есть несколько вариантов установки pyenv. Установка через PyPi пока еще не проверена большим количеством пользователей, так что рекомендуется выполнять установку непосредственно из репозитория.

Установка в ручном режиме описана на странице в github, однако проще использовать специально написанную утилиту pyenv-installer:


$ curl -L https://raw.githubusercontent.com/pyenv/pyenv-installer/master/bin/pyenv-installer | bash

После установки необходимо добавить в ~/.bashrc строки


export PATH="/home/user/.pyenv/bin:$PATH"
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"

Обновление:


$ pyenv update

pyenv устанавливается в $PYENV_ROOT (по-умолчанию: ~/.pyenv), так что для удаления достаточно просто удалить эту директорию:


$ rm -fr ~/.pyenv

и убрать из .bashrc строки


export PATH="~/.pyenv/bin:$PATH"
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"

Использование

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


$ pyenv
pyenv 1.1.5
Usage: pyenv  []

Some useful pyenv commands are:
   commands List all available pyenv
   ...

Посмотрим, какие версии python доступны для pyenv сразу же после установки:


$ pyenv versions
* system (set by /home/user/.pyenv/version)

pyenv показывает, что у нас имеется только одна версия python - системная. На самом деле их 2 (python и python3), но pyenv их объединяет и отображает одной записью.

Пробуем установить последнюю (на момент написания статьи) версию python:


$ pyenv install 3.6.3
Downloading Python-3.6.3.tar.xz...
-> https://www.python.org/ftp/python/3.6.3/Python-3.6.3.tar.xz
Installing Python-3.6.3...
Installed Python-3.6.3 to /home/user/.pyenv/versions/3.6.3

И убедимся, что она появилась в списке:


$ pyenv versions
* system (set by /home/user/.pyenv/version)
  3.6.3

Если в ходе установки в консоли появятся ошибки


WARNING: The Python bz2 extension was not compiled. Missing the bzip2 lib?
WARNING: The Python readline extension was not compiled. Missing the GNU readline lib?
WARNING: The Python sqlite3 extension was not compiled. Missing the SQLite3 lib?
Installed Python-3.6.3 to /home/user/.pyenv/versions/3.6.3
https://github.com/pyenv/pyenv/wiki/Common-build-problems

значит в системе установлены не все необходимые пакеты. Исправим это, выполнив


$ sudo apt-get install -y make build-essential libssl-dev zlib1g-dev libbz2-dev \
libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev \
xz-utils tk-dev

Попробуем установить python3.6 как версию по-умолчанию для нового проекта:


~$ mkdir my-project && cd my-project
~/my-project$ python -V    # версия python по-умолчанию
Python 2.7.12
~/my-project$ pyenv local 3.6.3    # меняем версию для директории my-project
~/my-project$ python -V    # новая версия
Python 3.6.3
~/my-project$ cd ..
~$ python -V    # для остальных директорий ничего не поменялось
Python 2.7.12
~$ ls -la ./my-project
-rw-rw-r-- 1 user user   10 ноя  8 17:18 .python-version
~$ cat ./.python-version
3.6.3

Virtualenv

Для работы с виртуальным окружением у pyenv имеется плагин pyenv-virtualenv. Если устанавливать pyenv при помощи скрипта, этот плагин устанавливается автоматически.

Создадим отдельное виртуальное окружение для работы с ansible и установим по-умолчанию его для директории с ansible-конфигами:


$ pyenv virtualenv 3.6.3 ansible
Requirement already satisfied: setuptools in /home/user/.pyenv/versions/3.6.3/envs/ansible/lib/python3.6/site-packages
Requirement already satisfied: pip in /home/user/.pyenv/versions/3.6.3/envs/ansible/lib/python3.6/site-packages
user@user:~/ansible$ pyenv versions
* system (set by /home/user/.pyenv/version)
  3.6.3
  3.6.3/envs/ansible
  ansible
user@user:~/ansible$ pyenv local ansible
(ansible) user@user:~/ansible$

Теперь при переходе в директорию ~/ansible будет автоматически активироваться виртуальное окружение и все python-команды будут направляться в $(pyenv root)/versions/3.6.3/envs/ansible/bin/python

Выводы

pyenv - безусловно удобный инструмент, рекомендованный к использованию как минимум в development-среде. Несомненный плюс этой утилиты в том, что она устанавливается в домашнюю директорию текущей учетной записи и не засоряет операционную систему.