Python: Настройка окружения
2026-02-26 15:07 Diff

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

Обсудим этот файл подробнее. Как мы уже обсуждали, в файле pyproject.toml указываются зависимости. При этом у каждой зависимости могут быть свои собственные зависимости, которые также обновляются и так до бесконечности.

Зависимости зависимостей называются транзитивными, и с ними все не просто. Система зависимостей может быть очень запутанной. Для такой ситуации придумали специальный термин — «ад зависимостей» или dependency hell.

Проблема заключается в том, что мы никак не фиксируем версии транзитивных зависимостей. Представим такой пример:

  • В нашем проекте есть зависимый пакет A с зафиксированной версией 1.3.2
  • У зависимости А есть зависимый пакет B с версией *

uv предоставляет команду uv sync, которая синхронизирует зависимости в проекте с теми, что указаны в pyproject.toml. Так без lock-файла команда uv sync поставила бы:

  • Для A — указанную версию
  • Для B — последнюю доступную версию из репозитория

Другими словами, выбор версии не детерминирован. Если автор обновит B и нарушит обратную совместимость, то пакет А перестанет работать — весь проект просто сломается.

Можно вручную отслеживать зависимости всех зависимостей и явно прописывать их версии в pyproject.toml. Но такой способ вряд ли сработает, потому что пакеты постоянно обновляются и меняются. Еще отслеживать вручную сложно, потому что связей слишком много — даже в проекте с пятью зависимостями будут сотни транзитивных зависимостей.

Другой выход — требовать, чтобы создатели всех библиотек всегда указывали версии. Этот вариант тоже не сработает, на этот раз из-за человеческого фактора.

Есть одно решение, которое точно сработает — это lock-файл. По сути это автоматизированное отслеживание зависимостей. Содержимое lock-файла выглядит примерно так:

Первый запуск установки зависимостей сформирует этот файл. Туда запишутся все установленные зависимости, в том числе транзитивные с версиями и хеш-суммами.

Так в примере выше, пакет pytest зависит от пакетов colorama, iniconfig, packaging и pluggy. И дальше в лок-файле записана информация об этих пакетах.

При дальнейших запусках команда uv sync всегда ставит то, что указано в lock-файле. Это сработает, даже если удалить папку .venv или добавить новые версии пакетов в файл pyproject.toml. Повторный запуск через любой промежуток времени приведет к тому же результату. Теперь с уверенностью можно сказать — проект запустится в любое время и для любого пользователя.

Версионирование

Поговорим об обновлении зависимостей. Для обновления всех зависимостей нужно выполнить команду uv sync --upgrade или кратко uv sync -U. Чтобы выполнить обновление конкретной зависимости — uv add -U name, где name — имя библиотеки. А как будет происходить обновление, зависит от того, что написано в pyproject.toml.

Рассмотрим все доступные варианты:

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

Если указан конкретный номер через ==, то версия библиотеки жестко зафиксирована и никакая команда не сможет обновить ее

Самый интересный сценарий происходит в случае использования тильды (~).

В семантическом версионировании считается, что patch, последняя цифра в версии, изменяется только в случае исправления ошибок, значит, обратная совместимость не должна теряться. При этом на практике это не всегда так. Код может работать с учетом ошибок в зависимостях.

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

Поэтому появился третий вариант. Добавление тильды приводит к тому, что в автоматическом режиме обновляются только патчи. Предположим что после добавления зависимости в проект, версия была установлена в ~2.10.3. Если после нее разработчики выпустили 2.10.5, то она будет установлена командой обновления.

То же самое произойдет, если потом будет выпущена версия 2.10.15. Но если создатель библиотеки опубликует изменения в мажорной и минорной версии, например, 2.11.5 или 3.0.0, то менеджер зависимостей их проигнорирует.

Примерно то же самое происходит и при использовании сочетания >= , <. Здесь фиксируется только мажорная версия, а минорную и патч разрешено обновлять.