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

в 18:55, , рубрики: egg, pip, python, virtualenv

Почему я ненавижу virtualenv и pipЯ не разделяю всеобщей любви к 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 разработчики снова смеются над вашими проблемами). Stupidest of all is if you’re using a virtualenv with –no-site-packages and compiling scores of python modules that you didn’t even write every time someone in your team wants to run up a dev environment in a 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 выдаёт вам список зависимостей как раз для вставки в 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

Об ошибках и поправках, а также о вариантах перевода непереведённого предложения в середине текста, мы можете невозбранно написать мне в личку.

Автор: lightman

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js