requirements.txt — что это и зачем?
В исходниках множества Python-проектов можно встретить этот странный текстовый файл. Например, им пользуются urllib3, numpy, pandas, flake8 и куча других проектов. Давайте разберемся, что это такое, как этим пользоваться и зачем нам это нужно.
Гипотетическая предыстория
Давайте представим, что вы написали замечательный скрипт, который спрашивает у пользователя название города и выводит текущую температуру и общее состояние погоды:
Скрипт получился настолько хорош, что вы хотите поделиться им со всеми своими друзьями. К сожалению, друзья при попытке запустить вашу программу получают следующую ошибку:
Кажется, что скинуть только код недостаточно.
Или, допустим, что вы сами через полгода-год попытаетесь запустить эту же программу. За это время вы успели пару раз переустановить Python, переустановить ОС, отформатировать свой магнитный накопитель (используйте SSD — нет, я серьёзно!) или может быть вообще сменили компьютер. Почти уверен, что при запуске скрипта вы получите ровно ту же самую ошибку.
Зачастую, когда мы пишем код, мы полагаемся на какие-либо библиотеки или фреймворки. Это со всех сторон хорошо — это удобно, уменьшает размер программы во много раз, позволяет не думать о мелких деталях, а решать свою конкретную задачу, опираясь на высокоуровневые абстракции. Но, к сожалению, есть «но» — такие библиотеки становятся частью вашей программы, ваш код становится зависим. Это значит, что ваш код больше не сможет работать сам по себе, для его работы должны быть установлены все зависимости.
Если ваша программа небольшая (состоит из пары файлов), то можно довольно легко просмотреть её глазами, найти там все инструкции import , отсеять из них импорты из стандартной библиотеки (вы ведь знаете модули стандартной библиотеки наизусть, да?), и таким образом восстановить список внешних зависимостей программы, которые устанавливаются через pip . Чем крупнее проект, тем сложнее это сделать. Бывают ситуации, когда по коду вообще нельзя понять, что ему нужна определенная зависимость.
Я хочу сказать, что намного мудрее составлять этот список зависимостей сразу же и просто поддерживать его в актуальном состоянии по мере развития проекта.
requirements.txt — это список внешних зависимостей
Сообщество Python исповедует идеологию «простое лучше, чем сложное». Наверное, поэтому для хранения списка зависимостей сообщество выбрало самый простой из возможных форматов — текстовый файл, где на каждой строке перечислено ровно по одной зависимости.
Стоит отметить, что requirements.txt не является стандартом, т.е. нет документа, который описывал бы требования к этому файлу. Скорее, это просто распространённая практика в сообществе, которая, наверное, возникла спонтанно и хорошо прижилась.
Не обязательно называть файл именно requirements.txt , можно назвать его как угодно, лишь бы его назначение оставалось понятно. Я встречал такие вариации, как requirements-dev.txt , test-requirements.txt , requirements/docs.txt и другие.
Вот пример самого простого такого файла (кстати, именно этим файлом можно описать зависимости, которые нужны для запуска нашего скрипта с погодой):
Если бы было несколько зависимостей, то файл выглядел бы так:
Можно указать конкретную версию зависимости. Если версия не указана, то считается, что нужна последняя доступная:
Можно указывать диапазоны и другие более сложные «спецификаторы версий». В целом, в requirements.txt можно писать любые «запросы», которые понимает команда pip install :
Как пользоваться
Команда pip install умеет читать такие файлы, если передать специальный флаг:
Таким образом, если requirements.txt будет иметь вот такое содержимое:
То следующие две команды будут иметь одинаковое действие:
Преимущества использования requirements.txt :
На таком маленьком примере разница может быть не очевидна, но когда список зависимостей разрастётся до определенного размера, то вам не захочется больше перечислять его в pip install напрямую.
Как бы ни поменялся файл requirements.txt , команда pip install -r requirements.txt не поменяется.
Так как это распространённое соглашение, то другим разработчикам будет достаточно увидеть этот файл, чтобы понять, что нужно сделать. Это здорово экономит время на чтении инструкций.
Как создать
Есть два подхода:
- создавать этот файл вручную;
- генерировать автоматически.
Главный принцип ручного подхода — если что-то поменялось в списке зависимостей (добавилась или удалилась зависимость, обновилась версия и т.д.), это изменение нужно отразить в requirements.txt .
Но можно использовать и встроенную в pip функциональность:
Команда pip freeze выводит все установленные в интерпретатор сторонние пакеты. Заметьте, что в список попали не только прямые зависимости ( pyowm ), но и подзависимости — это даже лучше, потому что вы сможете более точно воссоздать окружение по этому файлу.
Можно перенаправить вывод этой команды в файл при помощи стандартного консольного приема (работает и на Windows), и получить валидный файл requirements.txt :
Обратите внимание, что pip freeze выведет список пакетов в том окружении, в котором он запущен. Не забудьте активировать виртуальное окружение перед запуском этой команды, иначе получите список пакетов, установленных в глобальный интерпретатор. Кстати, у меня есть пост про виртуальные окружения, где объясняется как ими пользоваться.
Подытожим плюсы и минусы ручного и автоматического подходов:
- Вручную:
- минимальный файл, содержащий только прямые зависимости;
- можно указывать сложные спецификаторы версий;
- человеческий фактор — легко ошибиться или забыть что-нибудь;
- pip freeze :
- автоматически, быстро;
- автоматически добавляет версии установленных пакетов;
- в файл попадет всё дерево зависимостей, а не только прямые зависимости.
Можно использовать и смешанный подход: сгенерировать начальную версию файла при помощи pip freeze и допилить её затем руками, если у вас какая-то сложная нестандартная ситуация.
⚠️ Файл requirements.txt , конечно же, должен быть добавлен в систему контроля версий (git). Это такая же важная часть проекта, как и код. При этом виртуальное окружение не должно попадать в систему контроля версий. Оно должно воссоздаваться из requirements.txt .
Проблемы requirements.txt
Некоторые пакеты часто меняются, поэтому если вы не указываете конкретные версии, то в следующий раз при установке вы можете получить совсем другую среду. Это бывает особенно обидно, когда локально на машине разработчика всё работает правильно, но при деплое на боевой сервер программа либо работает иначе, либо вообще отказывается запускаться. Поэтому обязательно фиксируйте версии пакетов в requirements.txt — это сделает разные окружения хотя бы примерно похожими.
Почему «хотя бы примерно»? Практика показывает, что зафиксировать версию пакета недостаточно. Иногда случается, что под одной версией пакета в разное время может находиться совершенно разный код. PyPI, конечно, не позволит перезаписать уже опубликованную версию, но, например, ваш приватный корпоративный индекс пакетов может быть не таким строгим.
Чтобы действительно гарантировать, что вы устанавливаете те пакеты, что и ожидали, нужно рассчитывать и сверять их контрольные суммы. requirements.txt может содержать хэши пакетов, но, к сожалению, на данный момент нет простого стандартного способа как их туда положить, кроме как вручную (сложно). В качестве альтернативы опять предлагаю присмотреться к таким проектам, как poetry (хранит хэши в poetry.lock ) и pipenv (хранит хэши в Pipfile.lock ), где эта проблема решена хорошо, и вам не придётся переживать о воспроизводимости ваших сборок. Если всё-таки хочется добиться такого же эффекта при помощи requirements.txt , то можно посмотреть на такие проекты как pip-tools (пример использования) и hashin .
Заключение
requirements.txt — это очень популярный способ хранения списка внешних зависимостей проекта, поэтому вам определенно нужно уметь работать с такими файлами. Однако этот способ хранения списка зависимостей не лишён недостатков, поэтому если вы начинаете новый проект, то я предлагаю вам лучше использовать для этого poetry или pipenv .
Для тренировки можно попытаться запустить скрипт с погодой. Все исходники лежат здесь.
Дополнительное чтение
Конечно, я затронул лишь верхушку айсберга. На самом деле, requirements -файлы немножко сложнее.
Подпишитесь!
Чтобы получить уведомление о новом посте можно:
Источник
А как вам такой вариант управления зависимостями в Python?
Недавно я решил, что пора наконец-то разобраться в теме управления зависимостями в моих Python проектах и начал искать решение, которое бы меня полностью устроивало. Я поэкспериментировал с pipenv, проштудировал документацию к poetry, почитал другие статьи по теме. К сожалению, идеального решения я так и не нашел. В результате, я изобрел новый велосипед свой подход, который и предлагаю обсудить под катом.
Проблема
Но перед тем как непосредственно перейти к описанию подхода, мне бы хотелось объяснить, почему возникла такая необходимость и чем меня не устроивали существующие решения.
В рамках моей работы чаще всего я использую Python в двух целях: это либо анализ данных и машинное обучение используя блокноты Jupyter, либо небольшие Python скрипты, которые каким-то образом подготавливают данные. Хочу отметить, что я не занимаюсь созданием пакетов и их публикацией в Pypi (поэтому не акцентирую внимание на этом процессе в этой статье).
Очень часто мне требуется запускать скрипты или блокноты, которые были созданы довольно давно. Поэтому у меня возникла необходимость каким-то образом фиксировать версии зависимостей и запускать скрипты и блокноты в виртуальном окружении. С другой стороны, иногда в новой версии какой-либо библиотеки может появиться функциональность, которая позволит улучшить результаты старого блокнота или скрипта. Например, в scikit-learn (библиотека для машинного обучения) могут добавить имплементацию нового алгоритма, который отлично подходит для моего случая.
Очень часто при разработке какого-то скрипта, я также вынужден устанавливать какие-то дополнительные зависимости, которые требуются только для разработки. Например, так как я использую VSCode для разработки, то он требует, чтобы в виртуальном окружении был установлен pylint. Другие люди, с которыми я сотрудничаю, могут использовать другие инструменты для разработки, которым эта зависимость совершенно не требуется.
Исходя из этих предпосылок, у меня сложились следующие требования для управления зависимостями:
- У меня должна быть возможность разделять центральные зависимости (необходимые для запуска скрипта) и зависимости необходимые только для разработки.
- Я хочу указывать центральные зависимости без привязки к конкретной версии библиотеки. Таким образом, я смогу легко обновлять зависимости. С другой стороны, у меня должна быть возможность зафиксировать версии библиотек, чтобы полностью повторить мое виртуальное окружение.
- Один и тот же подход должен одинаково хорошо работать и для скриптов и для блокнотов.
Канонический подход по управлению зависимостями, когда создается отдельное виртуальное окружение для проекта и когда версии всех зависимостей фиксируются (используя pip freeze > requirements.txt ) в requirements.txt, не работает исходя из моих требований. Во-первых, он не позволяет разделить центральные и зависимости необходимые только для разработки. Во-вторых, в этом случае requirements.txt содержит все зависимости и их подзависимости, поэтому искать версию определенной библиотеки становится проблематичным.
Исходя из этих требований, оптимальным решением казалось использование pipenv (вот, например, статья о том, как использовать этот инструмент). Но когда я начал экспериментировать с ним, я выяснил, что у него есть несколько недостатков. Например, когда я использовал его для установки библиотек, он иногда полностью зависал или работал на редкость медленно. Поэтому после нескольких неудачных попыток, я решил дальше не продолжать с ним, хотя он идеально подходит исходя из моих требований. Poetry же не работает для управления зависимостями для блокнотов.
Решение
Прежде всего, хочу отметить, что я в качестве операционной системы использую (k)Ubuntu 18.04. Если у вас другая операционная система, возможно потребуется адаптация моего подхода. Если вы адаптируете этот подход под другую операционку, то было бы здорово, если бы вы поделились им в комментариях.
Для решение этой проблемы, я применяю следующий подход. Во-первых, я разделил центральные и зависимости для разработки. При установке я записываю имена зависимостей в два разных файла: requirements.txt содержит центральные зависимости, в то время как requirements-dev.txt хранит зависимости необходимые для разработки. Чтобы автоматизировать этот процесс, я написал bash функцию pip-install .
Чаще всего, я вызываю эту функцию следующим образом: pip-install scikit-learn или pip-install —dev pylint . В первом случае, эта функция устанавливает пакет scikit-learn используя pip и записывает имя пакета (которое вы написали) в файл requirements.txt. Во втором случае, имя пакета записывается в файл requirements-dev.txt. Стоит отметить, что так как эта функция используется при разработке, то я не указываю версию библиотеки, которую надо установить (в этом случае устанавливается последняя доступная версия). В последующем, в этом файле можно добавить ограничения на версию библиотеки вручную.
Когда мне требуется зафиксировать версии библиотек, я вызываю функцию pip-freeze . pip-freeze выбирает все зависимости из файла requirements.txt, фиксирует их версии и записывает результат в requirements.lock файл. После этого, мы можете легко восстановить свое виртуальное окружение на новой машине используя команду pip install -r requirements.lock . Команда pip-freeze —dev проделывает тот же фокус, только с зависимостями из requirements-dev.txt.
Таким образом, в репозитории вместе с проектом я храню 4 файла: requirements.txt, requirements-dev.txt, requirements.lock и requirements-dev.lock.
Исходный код этих двух функций хранится у меня в файле (Всегда проверяйте исходники. ). Вы можете скопировать его в директорию
/.bash/ , и чтобы сделать эти функции доступными у себя, добавить следующие строчки к себе в .bashrc :
Заключение
Я только начал использовать это решение и возможно ещё не обнаружил какие-то недостатки. В целом, этот пост задумывался для того, чтобы обсудить этот подход и есть ли у него права на жизнь. Если вы видите какие-то проблемы, то буду очень рад услышать о них.
Если вам интересно узнать о том, как я настраиваю свое Python окружение, то я написал очень длинную статью (на английском) по этой теме у себя в блоге.
Источник