Learning Platform
Урок 03.01 · 18 мин
Начальный
uvPython versionsVirtual environmentTooling
pip / uv / Poetry: внутренний resolver и lock-файлы pre-commit framework: shared hooks через YAML

Зачем углубляться в один инструмент

Во вводном модуле мы один раз настроили окружение: поставили uv пятью командами, собрали первый проект через uv init и запустили main.py через uv run. Этого хватило, чтобы было на чём писать код в первых уроках, но не хватит, чтобы понять, что происходит, когда коллега говорит: «слушай, у меня uv run собирает старый Python, я не могу твой скрипт запустить». Чтобы такие моменты не пугали, нужно представлять, как uv устроен внутри: где он хранит интерпретаторы, что считает «проектом», когда лезет в сеть, а когда нет.

Этот урок — про uv глазами Data Engineer, который через год будет править не только свой pyproject.toml, но и чужие. Никакой магии в инструменте нет, и это хорошая новость.

Что такое uv

uv
— это
single binary
, написанный на Rust компанией
Astral
— теми же людьми, что сделали линтер
ruff
. Один файл uv лежит у вас в ~/.local/bin/uv и заменяет собой длинный список:

  • pyenv — менеджер версий Python
  • pip — установщик пакетов
  • pip-tools — пиннинг зависимостей в lockfile
  • virtualenv / venv — создание изолированных окружений
  • poetry — высокоуровневый менеджер проектов
  • pipx — установка CLI-утилит на Python

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

Старый стек 2018 vs uv в 2026

Слева — что приходилось ставить отдельно. Справа — один uv заменяет всё.

2018-20236 инструментов
pyenvверсии Python
virtualenvизоляция
pipустановка пакетов
pip-toolslockfile
poetryпроект-манифест
pipxCLI-утилиты
20261 бинарник
uv pythonверсии Python
uv venvизоляция
uv addустановка пакетов
uv.locklockfile
pyproject.tomlпроект-манифест
uv tool / uvxCLI-утилиты

Это не маркетинг — uv действительно делает всё перечисленное. Скорость объясняется тремя вещами: Rust вместо Python (нет старта интерпретатора), параллельная установка пакетов, и собственный

resolver
, который не делает лишних HTTP-запросов.

Управление версиями Python

uv сам ставит Python. Никаких походов на python.org, никаких apt install python3.13:

uv python install 3.13

Команда скачивает

python-build-standalone
— это полная сборка CPython, которая не зависит от системных библиотек и одинаково работает на всех Linux/macOS/Windows. Распаковывается она в кэш uv:

  • macOS: ~/Library/Application Support/uv/python/
  • Linux: ~/.local/share/uv/python/
  • Windows: %LOCALAPPDATA%\uv\python\

Список установленных и доступных версий:

uv python list

Вы увидите большую таблицу. Установленные помечены путём до интерпретатора, остальные — <download available>. На любую можно поставить указатель через установку:

uv python install 3.12 3.13 3.14

Сразу три версии. На моей машине обычно стоят последние две стабильные и одна dev-сборка для проверки совместимости.

Какую версию выберет uv для проекта

Когда вы делаете uv run script.py, uv выбирает Python в таком порядке:

  1. Если рядом есть файл .python-version — берёт указанную там версию.
  2. Если в pyproject.toml есть поле requires-python — берёт самую новую совместимую.
  3. Иначе — берёт системную или последнюю установленную.

Поэтому пины делать стоит. В уроке 02 разберём, как это пишется в pyproject.toml. А .python-version создаётся автоматически командой:

uv python pin 3.13
TIP

Файл .python-version коммитится в git вместе с проектом. Это гарантирует, что коллега, который склонировал репозиторий, получит ту же версию Python, что и вы — без обсуждений в Slack.

Жизненный цикл проекта

Полный путь от пустой папки до работающего пакета:

mkdir my-etl
cd my-etl
uv init --python 3.13

uv init создаёт минимум файлов: pyproject.toml, .python-version, main.py, README.md, .gitignore. Что в каждом — разберём в следующем уроке про pyproject.toml. Сейчас важен сам факт: одна команда — и у вас валидный Python-проект.

Дальше добавляем библиотеки:

uv add requests
uv add "pydantic>=2.5"
uv add --dev pytest ruff

Что произошло после первого uv add:

  1. uv прочитал pyproject.toml, посмотрел, какие зависимости уже есть.
  2. Обратился в
    PyPI
    , узнал, какие версии requests существуют.
  3. Запустил resolver — выбрал последнюю совместимую с вашим Python 3.13 версию.
  4. Если .venv/ ещё не было — создал, поставил туда Python 3.13 и requests.
  5. Записал в uv.lock точные версии всех пакетов (включая транзитивные зависимости — requests тянет urllib3, certifi, charset-normalizer, idna).
  6. Обновил pyproject.toml, добавив "requests" в dependencies.
Что делает uv add

Один вызов команды затрагивает три файла и кэш.

uv add requestsкоманда
PyPIзапрос версий
resolverвыбор версии
.venv/установка
uv.lockзапись точных версий
pyproject.tomlдобавление в deps

Удалить пакет:

uv remove requests

Симметрично: убирает из pyproject.toml, обновляет lockfile, удаляет из .venv/. Чисто и без pip uninstall с подвисающими файлами.

uv sync — собрать окружение из манифеста

Представьте, что вы только что склонировали репозиторий с GitHub. В нём есть pyproject.toml и uv.lock, но нет .venv/. Одна команда:

uv sync

uv sync читает uv.lock, ставит ровно те версии, что записаны там, в свежий .venv/. После этого у вас локально точно такое же окружение, как у автора проекта. Это и есть «воспроизводимая сборка», ради которой существуют lockfile.

Если кто-то поправил pyproject.toml (добавил библиотеку) и закоммитил это в git, после git pull запустите uv sync — и ваш .venv/ подтянет изменения.

WARNING

Не вызывайте pip install внутри проекта с uv. Установленный таким способом пакет не попадёт в pyproject.toml и uv.lock, ваш uv sync его не воспроизведёт. На следующий день вы откроете проект — пакета нет, скрипт не работает.

uv lock — обновить lockfile без установки

uv lock

Только пересчитывает версии и пишет в uv.lock. Полезно в CI, когда нужно проверить, что lockfile в актуальном состоянии, без фактической установки .venv/.

Что делает uv run под капотом

uv run — самая частая команда, которой вы запускаете код. Шаги внутри:

  1. Найти ближайший вверх по дереву pyproject.toml. С этой папки начинается «проект».
  2. Если в проекте нет .venv/ или он устарел — пересоздать и засинковать.
  3. Если зависимости в pyproject.toml отличаются от установленных — досинкнуть (быстро, инкрементально).
  4. Выставить PATH так, чтобы python указывал на .venv/bin/python.
  5. Запустить переданную команду — python script.py, или python -m mypackage, или просто pytest.

Поэтому uv run pytest работает без явной активации окружения:

virtual environment
подхватывается автоматически.

Это поведение объясняет частую путаницу:

# 1. Запуск в проекте с uv — берётся .venv/ проекта
cd ~/projects/my-etl
uv run python -c "import requests; print(requests.__version__)"

# 2. Запуск вне проекта — uv создаст временный .venv/ в кэше
cd ~
uv run --with requests python -c "import requests; print(requests.__version__)"

Второй случай — это режим «эфемерный»: --with создаёт временное окружение в кэше uv (~/.cache/uv/), ставит туда requests, запускает скрипт, окружение остаётся в кэше для следующего раза. Полезно для разовых скриптов, когда заводить отдельный проект — overkill.

uv tool и uvx — CLI-утилиты

Часть Python-пакетов — это не библиотеки, а инструменты с собственной командной строкой: ruff, black, mypy, httpie. Их незачем ставить в .venv/ каждого проекта — они одинаковы везде и нужны глобально.

Для таких есть uv tool:

uv tool install ruff
uv tool install httpie

ruff теперь лежит в ~/.local/bin/ruff и доступен из любой папки. Каждый инструмент — в своём изолированном окружении, конфликты исключены.

Список установленных:

uv tool list

Запустить инструмент один раз без установки — uvx (короткое имя для uv tool run):

uvx black file.py
uvx ruff check .

uvx black file.py означает: возьми black из кэша или скачай в кэш, запусти black file.py, окружение оставь в кэше. Удобно для команд, которыми пользуетесь раз в месяц — не нужно засорять uv tool list.

Когда uv не подходит

Инструмент молодой, и есть кейсы, где он пока проигрывает старым подходам:

  • Legacy-проекты на poetry или conda, в которые вас взяли. Не перетаскивайте на uv без согласия команды — пиннинг версий через poetry.lock несовместим с uv.lock, и команда не оценит самовольную миграцию.
  • OS-package зависимости, которые ставятся через apt/brew (например, libpq-dev для psycopg2, geos для shapely). uv ставит чистый Python — системные библиотеки придётся ставить отдельно. К счастью, в современных пакетах типа psycopg[binary] или shapely уже всё запаковано.
  • Conda-only пакеты (mostly DS-стек: numba, pytorch со специфическими CUDA-сборками). uv пакеты из conda не понимает — либо ставьте через pip-зеркало внутри uv, либо для таких проектов оставайтесь на conda.
  • Корпоративные приватные индексы без поддержки PEP 503. Чаще всего поддержка есть, но если у вас Artifactory старой версии или Nexus с кастомными правилами — могут быть нюансы.

Для junior DE в 2026 году в 95% случаев uv подходит. Остальные 5% — узнаете по месту работы.

Шпаргалка команд

КомандаЧто делает
uv python install 3.13Скачать и установить Python 3.13
uv python listПоказать установленные и доступные Python
uv python pin 3.13Создать .python-version с пином версии
uv init --python 3.13Создать новый проект
uv add pkgДобавить зависимость в проект
uv add --dev pkgДобавить dev-зависимость
uv remove pkgУдалить зависимость
uv syncВоспроизвести окружение из uv.lock
uv lockПересчитать lockfile без установки
uv run cmdЗапустить команду в окружении проекта
uv tool install pkgУстановить CLI-утилиту глобально
uvx pkgЗапустить CLI-утилиту разово

Упражнение

  1. Установите две версии Python через uv:
uv python install 3.12 3.13
  1. Создайте две папки-песочницы — proj-old и proj-new. В первой запиньте Python 3.12, во второй — 3.13. Используйте uv init + uv python pin.

  2. В каждой запустите следующий скрипт version.py:

import sys
print(f"Python {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}")

Команда — uv run version.py.

Критерии приёмки:

  • proj-old печатает версию, начинающуюся с 3.12.
  • proj-new печатает версию, начинающуюся с 3.13.
  • В каждой папке есть файл .python-version с соответствующим пином.

В следующем уроке мы разберём pyproject.toml — манифест Python-проекта, в который uv всё пишет.

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5