Все статьи

uv для Python: замена pip, Poetry и pyenv в одном инструменте

uv для Python: замена pip, Poetry и pyenv в одном инструменте

Ещё недавно типичный Python-проект требовал целого зоопарка: venv или virtualenv для окружения, pip и requirements.txt для зависимостей, pip-tools или Poetry для lock-файла, pyenv для разных версий Python, pipx для утилит вроде линтеров. Каждый инструмент со своими командами и своими поломками. Я долго сидел на Poetry, и он не раз ломал сам себя при обновлении.

uv от Astral, авторов линтера Ruff, заменяет всё это одним быстрым бинарником. Ниже — что именно он умеет, как перевести на него проект и почему я постепенно перехожу на него сам. Кстати, бэкенд этой платформы тоже собирается через uv.

Что заменяет uv

uv закрывает задачи сразу нескольких инструментов:

Задача Раньше В uv
Установка пакетов pip uv add, uv pip install
Lock-файл и точные версии pip-tools, Poetry uv lock, uv sync
Виртуальное окружение venv, virtualenv создаётся автоматически
Несколько версий Python pyenv uv python install
Изолированные утилиты pipx uv tool install, uvx
Сборка и публикация пакета build, twine uv build, uv publish

Второй аргумент — скорость. uv написан на Rust и активно использует глобальный кеш. По данным разработчиков, он в 10–100 раз быстрее pip; в их бенчмарках заметно опережает и Poetry, особенно при установке «с нуля», без кеша. Разница особенно чувствуется в Docker и CI, где зависимости ставятся десятки раз в день.

Установка и обновление

На Linux и macOS:

curl -LsSf https://astral.sh/uv/install.sh | sh

На Windows — через PowerShell:

powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

uv также ставится через pip, pipx и пакетные менеджеры дистрибутивов. Обновление — одной командой, без переустановки:

uv self update

Документация на docs.astral.sh/uv подробнее, чем README репозитория, — начинать лучше с неё.

Новый проект и зависимости

В каталоге проекта:

uv init

uv создаёт pyproject.toml, .python-version, README.md, пример main.py и .gitignore. В pyproject.toml указаны имя проекта (по имени каталога), версия, требуемая версия Python и пустой список зависимостей. Чтобы сразу создать проект под нужную версию Python:

uv init example --python 3.11

Добавление и удаление зависимостей:

uv add requests
uv remove requests

При первом uv add появляются каталог .venv с обычным виртуальным окружением и файл uv.lock с точными версиями всех пакетов и их зависимостей. Повторная установка пакета из кеша занимает доли секунды.

Lock-файл и воспроизводимость

uv.lock — главное, ради чего стоит переходить. В нём зафиксированы версии и хеши всех пакетов, включая транзитивные. Если скопировать в новый каталог pyproject.toml, uv.lock и код, достаточно одной команды:

uv sync

uv создаст окружение и поставит ровно те же версии, что были у вас. Это защищает от ситуации «у меня работает, а на сервере нет»: новая версия библиотеки с изменённым API больше не приезжает внезапно. Lock-файл нужно коммитить в git, а актуализировать его можно командой uv lock — например, в pre-commit-хуке.

Посмотреть дерево зависимостей:

uv tree

Запуск кода через uv run

Частая ошибка новичков: пакет добавлен через uv add pandas, а python main.py падает с ModuleNotFoundError. Причина — системный интерпретатор не знает про .venv проекта. Правильно запускать так:

uv run main.py
uv run main.py --input data.csv     # аргументы передаются как обычно

uv run сам проверяет, что окружение синхронизировано с lock-файлом, и запускает код в нём. Активировать окружение вручную не нужно.

Версии Python вместо pyenv

uv умеет скачивать и переключать интерпретаторы, в том числе PyPy:

uv python list                  # установленные и доступные версии
uv python install 3.13          # установить версию
uv python uninstall 3.13        # удалить
uv run --python 3.12 main.py    # разово запустить на другой версии
uv python pin 3.13              # версия по умолчанию для проекта

uv python pin переписывает .python-version. Если версия не совпадает с ограничением requires-python в pyproject.toml, uv подскажет, что поправить. При смене версии uv сам пересоздаёт окружение — без ручного удаления .venv.

Утилиты через uvx и tool install

Линтеры, форматтеры и другие CLI-утилиты не нужно ставить в зависимости проекта:

uvx ruff check .            # запустить Ruff во временном изолированном окружении
uv tool install ruff        # установить утилиту глобально, но изолированно

Утилита работает в любом каталоге, но не попадает в зависимости ни одного проекта. Это прямая замена pipx.

uv в Docker и CI

У uv есть официальные Docker-образы, а в Dockerfile его удобно копировать готовым бинарником:

FROM python:3.13-slim
COPY --from=ghcr.io/astral-sh/uv:0.13.4 /uv /uvx /bin/

WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev
COPY . .
CMD ["uv", "run", "main.py"]

Флаг --frozen запрещает менять lock-файл при сборке, а --no-dev пропускает зависимости для разработки. Тег образа лучше закреплять на конкретной версии, а не latest, чтобы сборка не менялась сама. Для GitHub Actions есть официальный экшен astral-sh/setup-uv, а в документации описаны интеграции с pre-commit, Docker Compose, FastAPI и Jupyter.

Минусы и с чем быть осторожным

  • Молодой инструмент. uv развивается очень быстро, команды и поведение иногда меняются между версиями — читайте changelog при обновлении.
  • Свой lock-формат. uv.lock не читается Poetry и pip напрямую; для старых систем можно выгрузить requirements.txt командой uv export.
  • Привычки. Команда python main.py без uv run будет использовать не то окружение — это нужно один раз запомнить.

Итог

uv заменяет pip, pip-tools, Poetry, pyenv, pipx и virtualenv одним инструментом и делает это заметно быстрее. Lock-файл даёт воспроизводимые установки, uv run избавляет от ручной активации окружений, а управление версиями Python встроено из коробки. Для нового проекта я бы начинал сразу с uv, а старый можно перевести за вечер: uv init, перенести зависимости через uv add, закоммитить uv.lock.

Если вы организуете проект и хотите, чтобы код внутри был таким же аккуратным, как его окружение, — классы, модули, SOLID и паттерны разобраны в моём курсе по Python ООП. А для бэкапов самих проектов пригодится связка Syncthing и правило 3-2-1.

Источники