Pull to refresh

Почему я ненавижу virtualenv и pip

Reading time 9 min
Views 101K
Original author: Andrew, platform-agnostic python developer
Я не разделяю всеобщей любви к virtualenv (далее — venv) и pip. Я считаю, что они лишь вносят неразбериху и более того — вредят. Python-программисты чаще всего не соглашаются со мной, да и venv+pip де-факто считается стандартом в python-сообществе. Так как я понимаю, насколько голословными звучат мои высказывания, решил написать сей трактат. Конечно, я иногда пускаюсь спорить на эту тему и в реальной жизни: и ну нравится мне заводить людей и наблюдать, как страстно они остаивают свою позицию. Но при этом мне всегда кажется, что словесно я не могу обосновать свою позицию в полной мере. Поэтому вместо того, чтобы постоянно пытаться вербально доказывать свою точку зрения, я решил написать эту статью, дабы потом просто показывать её людям. Может быть тогда некоторые со мной согласятся, потому что сейчас не согласен почти никто. А может наоборот, как только мои доводы будут всецело поняты, найдутся те, кто их аргументированно опровергнет. Так или иначе, я буду рад любому варианту развития событий.

venv и иллюзия изоляции

Изоляция и легко воспроизводимое чистое python-окружение безо всяких скрытых зависимостей от основной операционной системы это, определённо, хорошая вещь. Основная цель venv — предоставление удобного способа изоляции на уровне python'а. Но тут не всё идеально: для тех пакетов питона, которые зависимы от системных библиотек, изоляция осуществляется лишь частично, распространясь только на python-составляющую этих пакетов. Если разработчик в курсе этого — всё еще не так плохо, но вот если нет — он может столкнуться с серьёзными и непонятными ему проблемами.

Полные методы изоляции приводят к избыточности venv

Есть несколько методов изоляции всей файловой системы. Самый полный, но тяжеловесный способ — это использование виртуальной машины под гипервизором. Такую функциональность обеспечивает ряд программ, таких как Vagrant. С другой стороны существуют легковесные решения, например chroot, либо легковесные контейнеры, работающие на уровне операционной системы, например на Linux это LXC. Причём LXC может использовать copy-on-write файловую систему вроде btrfs для создания среды окружения с большей скоростью и меньшими затратами дискового пространства, чем даже в случае с venv.

venv это антипаттерн развёртывания

Я чувствую раздражение некоторых читателей при упоминании таких технологий как LXC. Да, на практике мы не всегда можем обеспечить совместимость нашей целевой среды окружения с LXC. И не всегда мы можем предоставить LXC требуемые ей права суперадмина (и ведь всё это лишь для того, чтобы просто развернуть наше приложение!)
Но я считаю, что и venv тоже не подходит для развёртывания. Почему? Как упоминалось в начале, изначальная цель venv — это лишь обеспечение удобного доступа пользователя к интерактивно создаваемой python-песочнице. Развёртывание же это по крайней мере полуавтоматический и легко повторяемый процесс. Таким образом попытка автоматизировать venv, чтобы заставить его автоматически делать то, что на нём удобнее делать вручную, видится более сложной и нетривиальной задачей, нежели просто установить переменную окружения PYTHONPATH в качестве входной точки программы. Очень просто установить через pip какой-нибудь огромный пакет, вроде Django, в произвольную папку (через опцию prefix). По крайней мере гораздо проще, чем косвенно управлять venv и путаться с многочисленными shebang'ами. И не забывайте о том, что с venv по сути у вас отсутствует контроль за целевой средой окружения, и вам приходится вежливо просить админинистратора компьютера, на котором происходит развёртывание, чтобы тот установил в саму операционную систему клиентские mysql библиотеки и заголовочные файлы; всё это для того, чтобы вы просто смогли скомпилировать mysql-python для развёртывания!
Распространять коммерческое ПО непросто, и venv в этом не помощник.

venv полон костылей

Когда вы устанавливаете venv, он на самом деле не пуст. В директорию lib/ копируется вся стандартная библиотека питона. В include/ — пачка питоньих заголовочных файлов. Смысл существования этих директорий кажется мне надуманным (подробнее в следующем параграфе), но гораздо больше меня раздражает bin/. В bin/ лежат pip и easy_install. venv портит shebang'и их обоих, чтобы запускать их не под системным, а под лежащим в той же директории интерпретатором python'а. Shebang'и всех прочих скриптов от дополнительно установленных пакетов портятся точно таким же образом. И вам приходится поддерживать это поведение venv и следить за shebang'ами постоянно, пока вам нужно работать со скриптами, лежащими внутри venv, «снаружи», например, запуская их через системный cron. Вам приходится «захардкоживать» путь к соответствующим venv, чтобы скрипт запускался под нужным интерпретатором. Это, как минимум, так же утомительно, как и вручную настраивать PATH/PYTHONPATH. На самом деле, проще ничего не делать, но я вернусь к этому чуть позже.

Ой, я забыл упомянуть bin/activate

Который устанавливает переменную окружения PATH и меняет вашу строку ввода в консоли. Если вам всегда было по нраву это, и вы считали это передовой технологией, что ж, поздравляю, похоже, вы жили в танке. Впрочем, как и ваш скрипт. .NET разработчики под Windows смеются над вами.

--no-site-packages

venv уродует sys.path двумя способами. Опция --sytem-site-packages присоединяет site-packages venv'а в начало существующего списка путей, тем самым, становится возможным использовать глобально установленные модули питона внутри venv. Есть также опция --no-site-packages, которая включена по умолчанию, и которая, как можно догадаться, этого присоединения не совершает. Видимо, именно поэтому копии некоторых библиотек вроде stdlib и заголовочных файлов положены беспорядочно свалены прямо внутри venv. Собственно, факт существования этой опции, а также тот факт, что она выставлена по умолчанию, на мой взгляд, говорят сами за себя. Очевидно, сторонники venv не желают иметь скрытые зависимости между пакетами в системе и venv; как и не желают, чтобы пакеты неправильных версий случайно просачивались внутрь venv. Однако их любимый venv всегда появляется в самом начале списка путей, так что небольшая вероятность всё равно присутствует (нет, я не забыл о команде pip freeze — о ней поговорим позже). Это опасение может показаться излишним, но вот в чём парадокс. По сути, venv никогда и не обеспечивал 100% изоляцию! Какая польза от того, чтобы вы на 100% уверены в том, что вы не используете системную версию mysql-python в то время, как вы на 100% уверены, что используете системную версию libmysqlclient! Невозможно одновременно частично использовать изоляцию и частично игнорировать её!

pip и venv это отличная связка

Все так думают лишь потому, что они написаны одним и тем же человеком — Ian Bicking. У обеих программ своя собственная философия и свои собственные варианты использования. Мне не нравится venv по большей части потому, что он заставляет людей верить, но я допускаю, что у него есть своя ниша. На самом деле я сам им пользуюсь время от времени для быстрых одноразовых тестов. Но pip с другой стороны не должен был рождаться вообще. Он — лишь «почти-совместимая» альтернатива easy_install с дополнительными свистоперделками, которых бы лучше не было вовсе. Заместо него я предпочитаю использовать easy_install вкупе с такими интерактивными и не очень программами как puppet или вообще компиляцией пакетов из исходников. Может показаться, что у меня предвзятость против pip, но это не так. Я согласен, что в чём-то приятнее писать в консоли pip install, нежели easy_install. easy_install звучит как-то глупо. Да и нижнее подчёркивание в имени это явно не практично. Готов поспорить, что одно лишь имя обеспечивает pip'у некоторую часть его популярности.

pip каждый раз собирает из исходников

eggs в питоне это как jars в джаве
Кажется, pip умышленно был лишён возможности easy_install устанавливать пакеты из бинарников (eggs). Несмотря на то, что распространение бинарников было значимой частью python-платформы и, кстати, вполне работоспособной, видимо, кто-то решил, что это плохая идея. Конечно, с точки зрения разработчиков, компиляция пакетов из исходников есть очевидное благо, которое позволяет им не компилировать предварительно пакет под каждую из всех поддерживаемых платформ (а переложить это на несомненно обрадованного этим пользователя — прим. пер.). Но компиляция становится злом в том случае, если целевых платформ немного, и вы точно знаете её/их и хотели бы собрать пакет заранее, избавившись от необходимости иметь компилятор на целевом компьютере (.NET- и Java-разработчики снова смеются над вашими проблемами). Но самая большая тупость в том, что если вы используете venv с опцией --no-site-packages, то каждый раз каждому члену вашей команды, развёртывая venv внутри SOE в ходе процесса разработки, приходится пересобирать все модули. И это поистине глупо, ведь вы даже их не разрабатываете и ни малейшего смысла в постоянной пересборке просто нет.

Этот чёртов requirements.txt

Чтобы объявить необходимые зависимости для своего пакета, можно указать их в install_requires в setup.py. Это python way. setuptools/distribute реализуют этот механизм, и он используется как easy_install, так и pip для автоматической загрузки с Pypi и установки этих зависимостей. По причинам, которые слишком долго объяснять, pip также позволяет указать список зависимостей в текстовом файле. Обычно он называется requirements.txt. Его синтаксис точно такой же, как в setup.py, но у него также есть возможность дополнительно вложить файлы, в которых пути к зависимостям могут быть указаны в виде файловых путей, URI и даже ссылок на Mercurial- / Git-репозитории (про всё это мы поговорим в следующем параграфе).

Я согласен, что эти функции здорово расширяют возможности, но я не верю, что именно они являются причиной существования requirements.txt. По-моему, реальная причина в том, что все Python-проекты делятся на два класса: пакеты, которые не используются самостоятельно и лишь импортируются в существующие проекты и, собственно, сами эти проекты. Те разработчики, которые пишут лишь приложения, не вполне понимают все особенности создания пакетов, поэтому, недолго думая, просто «захардкоживают» весь ассортимент используемых ими модулей в своё приложение, просто перечисляя их в requirements.txt, ведь это так удобно! Эти разработчики чаще всего просто советуют пользователям установить venv, а затем накатить в него свой пакет командой pip install -r requirements.txt.

В результате мы имеем некоторое количество python-разработчиков, которые считают requirements.txt панацеей от всех проблем. Они даже никогда не узнают о существовании setuptools. Их легко покоряет кажущимся простым тупое перечисление ссылок на необходимые пакеты, лежащие где-то в недрах интернета: на сайтах или в системах контроля версий. Меня обескураживает их святая уверенность в «фантастической» прагматичности этого подхода и вытекающем отсюда желании пропагандировать использование virtualenv+pip как связки незаменимых инструментов для всех и каждого.

URI в качестве путей к зависимостям это отстой

setuptools позволяет вам указать имя и необходимую версию пакета, который по умолчанию скачивается с Pypi. Pypi обеспечивает индексацию, но вы можете создать и свой собственный индекс (в виде простых HTML страниц) и указать, что информацию следует извлекать в первую очередь из них, а не с сайта Pypi. Кто бы ни разработал эту технологию, он пытался предоставить разработчику возможность привязываться к именам пакетов, а не их физическому местоположению или веб-протоколу. И он мыслил правильно.

Если вы в requirements.txt указываете путь к локальному файлу или к лежащему на каком-нибудь сайте тарболлу, по факту вы захардкоживаете эту ссылку. Хотя в данном случае лучшим выходом было бы использование репозитория пакетов. Который позволил бы людям, например, настроить зеркала на него в своей локальной сети. Кроме того, вы не можете указать минимальную версию, лишь только точную текущую. А в один прекрасный день тот самый файлик с пакетом переместится или удалится, в общем, пропадёт, и код внезапно перестанет работать. Совершенно очевидно, что мы этого не хотим, ведь так?

Что ж, есть другой способ. Давайте указывать зависимости таким способом:
git+https://github.org/my/stupid/fucking/package#egg=1.2.3

Но он требует от пользователя иметь на компьютере git, кроме того pip'у приходится выкачивать полную копию репозитория. А чаще люди и вовсе не используют версионную нотацию (1.2.3 в примере — прим. пер.) и предполагают, что стабильная версия должна лежать в ветке master. Всё это печально. Я знаю, что сейчас модно ставить всё подряд прямо из систем контроля версий, но «хардкордить» эти URL в ваш проект? И без того спорное решение, становящееся совсем неоправданным, если всё можно сделать правильно, просто немного попотев над правильной настройкой setup.py.

Если вам по нраву pip freeze, с вами что-то не так

У меня хорошо получается отслеживать свои зависимости и управлять ими. Я делаю это с помощью pip freeze. Команду pip freeze используют для того, чтобы убедиться, что никакие Python-зависимости не были упущены посреди цикла разработки. Если вы полагаете, что pip freeze выдаёт вам список зависимостей как раз для вставки в requirements.txt (который, напомню, не нужен) — тогда вы просто используете --no-site-packages (который тоже не нужен) при создании нового venv, и весь набор зависимостей всё равно получается глобально-системным, а не питоньим. А, и кроме того, таким образом не узнать, какие из ваших зависимостей установлены напрямую, а какие подтянуты другими.

С другой стороны можно, обнаружив, что эти зависимости порушили вашу среду окружения, попытаться просто пересоздать её. Но с venv+pip это займёт у вас целую вечность (напомню, нужно будет пересобрать всё и вся). В то время как с LXC CoW и уже собранными в бинарные eggs пакетами (всех зависимостей, с которыми вы не работаете в данный момент), вы очень быстро обнаружите недостающие зависимости — как на системном уровне, так и непосредственно питоньи.
В целом pip freeze не такая уж плохая команда, всё дело в том, что люди слишком часто считают её незаменимой, не принимают во внимание её недостатки и используют не по назначению.

Заключение

Это мой критикующий, при этом полностью субъективный и, может, в чём-то даже спорный анализ полезности как программ virtualenv и pip, так и сложившейся вокруг них программерской культуры. Мне очень нравится питон как язык, но меньше — как платформа, потому что она фрагментирована различными стандартами распространения пакетов и стандартами процесса разработки. Лично в моём случае это приводит к тому, что я трачу на борьбу с Питоном больше времени, нежели на работу с ним. Я регулярно общаюсь с разными умными людьми, которые искренне верят, что venv и pip обеспечивают всё, что им нужно для разработки, совместной работы и развёртывания готовых приложений. Я же во время разработки не использую ни venv, ни pip.
И я надеюсь, что эта статья, как минимум, докажет читателю, что можно и нужно понимать принцип работы этих программ и при этом критически относиться к ним.

От переводчика:
Разработчикам, работающим под Windows: независимо от того, решили вы отказаться от pip или просто ищете способ устанавливать некоторые пакеты, которые не хотят сходу ставиться с pip (например падающие с ошибкой unable to find vcvarsall.bat), а на сайтах разработчиков пакетов, скомпилированные версии не предоставляются, могу посоветовать чудесный сайт, собирающий под своим крылом всевозможные скомпилированные пакеты во всевозможных версиях: Unofficial Windows Binaries for Python Extension Packages
Tags:
Hubs:
+50
Comments 61
Comments Comments 61

Articles