TL;DR


Чтобы просто установить и изолировать зависимости проекта, virtualenv часто оказывается слишком тяжелым решением. Предлагаем простую альтернативу:
  1. добавить ./.pip в переменную окружения PYTHONPATH,
  2. установить пакеты локально с помощью pip install -t .pip,
  3. запускать python из папки проекта.


Какую версию желаете сегодня?


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

Обычная практика — включать в проект файл requirements.txt. В этом файле перечислены все библиотеки, от которых зависит проект, с номерами версий. Если файл на месте, установить зависимости просто:
$ pip install -r requirements.txt

Пока все хорошо. Плохо станет, если у вас два и больше проектов с конфликтующими зависимостями. Допустим, проект A работает с библиотекой X только версии 0.1, а проект B — 0.2. По умолчанию pip устанавливает библиотеки глобально в папку интерпретатора Питона. Это означает, что команда $ pip install X==0.2 установит библиотеку X версии 0.2 для всех проектов и перезапишет версию 0.1, если та уже установлена. Переключение между A и B означает глобальную переустановку X каждый раз, что долго и неудобно.

Остров под солнцем


Популярное решение этой частой проблемы — виртуальные окружения. Фреймворк virtualenv создает изолированные Питон-окружения. Зависимости каждого проекта отделяются друг от друга. Так или иначе, некоторым пользователям virtualenv кажется слишком сложным. Поэтому существуют пакеты типа virtualenvwrapper и autoenv, которые расширяют функциональность virtualenv, чтобы им было проще пользоваться. Другие решения: Anaconda environments и pyvenv из стандартной библиотеки Питона (3.3+).

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

В мире Яваскрипта npm и Bower предлагают простое, надежное и мощное управление пакетами, которого как будто не хватает в Питоне. Их ключ к успеху в том, что они скачивают правильную версию правильной библиотеки и кладут ее в специальную папку внутри проекта. Скачанные библиотеки относятся только к конкретному проекту. Это автоматически избавляет от проблем, о которых говорилось выше.

Чистая магия


Оказывается, есть простой способ повторить подход npm и Bower в Питоне:
  • добавить ./.pip в PYTHONPATH,
  • использовать pip с параметром -t .pip для локальной установки пакетов.

Затем просто запускаете код из папки проекта. Забудьте про source env/bin/activate и deactivate!

Фокус работает, потому что ./.pip — относительный путь. В результате, когда запускаете python из ~/dev/project_a, папка ~/dev/project_a/.pip включается в список папок библиотек для этого экземпляра Питона. Запускаете python в ~/dev/project_b — включается ~/dev/project_b/.pip. Прием работает на всех популярных платформах: Линуксе, Маке и Виндоузе.

Название папки .pip, конечно, может быть любым. Кому-то больше понравится pip_components или libs. Тем не менее, .pip быстро печатать, а точка в начале делает папку скрытой в Линуксе и Маке.

Шаг 1: Установить PYTHONPATH


  • MAC/LINUX
    Эта команда установит PYTHONPATH навсегда для стандартных терминальных сессий:
    $ echo 'export PYTHONPATH="./.pip:$PYTHONPATH"' >> ~/.bash_profile
    

    После этого перезапустите терминал или исполните $ source .bash_profile, чтобы PYTHONPATH загрузилась в активной сессии. В зависимости от платформы вам, возможно, надо будет заменить ~/.bash_profile на ~/.bashrc.

  • WINDOWS
    Окройте Панель упраления и перейдите в System and Security > System > Change Settings > Advanced > Environment Variables (у меня английская версия, поэтому не перевожу названия разделов, чтобы случайно не запутать читателей. — прим. пер.). Добавьте или отредактируйте переменную PYTHONPATH, чтобы ее значение стало таким: .\.pip или .\.pip;(...other paths...). Можно установить переменную как для пользователя, так и для всей системы.

  • TEMPORARY PYTHONPATH
    Если предпочитаете устанавливать PYTHONPATH только на время сессии, запустите $ export PYTHONPATH=./.pip в Маке и Линуксе или > set PYTHONPATH=.\.pip в Виндовсе.

    В Маке и Линуксе можно даже установить PYTHONPATH только на время Питон-сессии: $ PYTHONPATH=./.pip python main.py.


Шаг 2: Установить пакеты через pip -t


Теперь, когда PYTHONPATH установлена, осталось только установить пакеты в правильное место с помощью pip. Для этого используем ключ -t или --target:
$ cd project_a
project_a$ pip install requests==2.7.0 -t .pip

project_a$ python
>>> import requests
>>> requests.__version__
'2.7.0'

Теперь то же самое в другом проекте с другой версией:
$ cd project_b
project_b$ pip install requests==2.6.0 -t .pip

project_b$ python
>>> import requests
>>> requests.__version__
'2.6.0'

С файлом requirements.txt все работает так же:
$ pip install -r requirements.txt -t .pip


Потенциальные засады


Разные интерпретаторы Питона


Можно легко запустить программу другим интерпретатором Питона:
$ /path/to/python main.py

Тем не менее, есть проблема с переключением между Питоном 2 и 3, если используются пакеты, которые компилируют исходный код во время установки через 2to3. У таких пакетов нет единой кодовой базы, при установке код генерируется соответственно активной версии Питона.

В этом случае создайте папку типа .pip3 и добавьте ее в начало PYTHONPATH, когда запускаете код Питоном 3.

easy_install


Если у вас есть пакеты, установленные глобально через easy_install, столкнетесь с проблемой: easy_install дописывает путь к таким пакетам в начало sys.path, поэтому у них приоритет над пакетами из .pip.

Решение — избавиться от пакетов, установленных глобально через easy_install.

Чтобы проверить, что ничто не мешает использовать .pip, запустите import sys;sys.path в Питоне. Если перед ./.pip будут другие пути, возможно, придется сначала почистить систему от глобальных easy_install-пакетов.

Комментарии (12)


  1. rhamdeew
    27.06.2015 22:04

    Довольно элегантное решение. В Node.js оно радует. Посмотрим обрадует ли в этом случае)


  1. ZyXI
    28.06.2015 01:51
    +1

    Вот не нужно так делать:

    export PYTHONPATH="./.pip:$PYTHONPATH"

    . Данное действие, скорее всего, добавит текущий каталог в PYTHONPATH, но, возможно и нет. Зависит от того, пуста ли переменная PYTHONPATH. Если вам нужно добавить что?то в переменные типа *PATH, то используйте ${VARNAME:+:} вместо двоеточия, чтобы не получить в результате пустой элемент, который часто имеет значение вроде «текущий каталог» или «каталог(и) по?умолчанию».


  1. KhodeN
    28.06.2015 01:55
    +1

    Мне хватает pyenv c штатным плагином virtualenv.
    Позволяет хранить на машине сколько угодно версий python, встраивается в командный шелл, автоматически включает окружение и переходе в папку и выключает при выходе из неё.

    С pip -t одна сложность — надо не забывать писать параметр. Лучше сделать алиас уже с параметром.


  1. gbezyuk
    28.06.2015 06:47
    +4

    >> Чтобы просто установить и изолировать зависимости проекта, virtualenv часто оказывается слишком тяжелым решением.
    серьёзно? можно реальный пример?


    1. moigagoo Автор
      28.06.2015 06:58
      -7

      «Тяжесть» решения — субъективная величина. Мне virtualenv через несколько лет использования действительно стал казаться слишком тяжелым для моих задач. Неохота стало делать activate и deactivate. Virtualenvwrapper мне не нравится, да и в Павершелле не работает, а я сейчас почти всегда в нем. Я перестал им пользоваться этими инструментами и начал ставить пакеты глобально. Да, это плохая практика, но мне не мешало. В моем мире virtualenv — слишком тяжелый инструмент.

      Теперь, когда узнал о таком простом приеме, начал использовать его — и не пожалел.

      Если в вашем мире нет примеров, когда virtualenv — это слишком тяжело, пользуйтесь virtualenv ради бога. Никто ж не запрещает. Значит, в вашем мире это идеальный инструмент.


  1. MrFrizzy
    28.06.2015 10:59
    +1

    Еще такую штуку рекомендую: nix


    1. kmmbvnr
      28.06.2015 12:44
      +1

      ээ, и оно с питон пакетами как-то дружит?


      1. JagaJaga
        28.06.2015 16:17

        С чем он только не дружит.
        nix-shell так вообще бомба.


      1. MrFrizzy
        28.06.2015 18:42

        Присоединяюсь к предыдущему комментарию.
        По теме: вот ссылки с первой страницы гугла по запросу «nix packet manager python»:
        Getting started with Nix package manager
        RhodeCode and Nix Package Manager
        Nixos Container Example: Python Web Development


        1. kmmbvnr
          29.06.2015 03:39

          >> The following environment can be used in a nixos-container for using traditional python package managers (pip, buildout etc.) in an impure way within a container

          Т.е. тот же virtual env, те же не атомарные апдейты, только надо еще затратить усилия, чтоб заставить это работать. Но зачем?


          1. MrFrizzy
            29.06.2015 19:40
            -1

            Это как минимум не хуже venv, а в преспективе — лучше.


  1. aikikode
    28.06.2015 21:08
    +1

    Интересное решение, но насколько я понимаю, в отличие от того же virtualenv, оно будет работать только в корне проекта.