Зачем углубляться в один инструмент
Во вводном модуле мы один раз настроили окружение: поставили uv пятью командами, собрали первый проект через uv init и запустили main.py через uv run. Этого хватило, чтобы было на чём писать код в первых уроках, но не хватит, чтобы понять, что происходит, когда коллега говорит: «слушай, у меня uv run собирает старый Python, я не могу твой скрипт запустить». Чтобы такие моменты не пугали, нужно представлять, как uv устроен внутри: где он хранит интерпретаторы, что считает «проектом», когда лезет в сеть, а когда нет.
Этот урок — про uv глазами Data Engineer, который через год будет править не только свой pyproject.toml, но и чужие. Никакой магии в инструменте нет, и это хорошая новость.
Что такое uv
uv лежит у вас в ~/.local/bin/uv и заменяет собой длинный список:
pyenv— менеджер версий Pythonpip— установщик пакетовpip-tools— пиннинг зависимостей в lockfilevirtualenv/venv— создание изолированных окруженийpoetry— высокоуровневый менеджер проектовpipx— установка CLI-утилит на Python
Раньше у разработчика на машине было пять-шесть таких инструментов, каждый со своими конфигами, своими багами, своими версиями. С uv остаётся один.
Слева — что приходилось ставить отдельно. Справа — один uv заменяет всё.
Это не маркетинг — uv действительно делает всё перечисленное. Скорость объясняется тремя вещами: Rust вместо Python (нет старта интерпретатора), параллельная установка пакетов, и собственный
Управление версиями Python
uv сам ставит Python. Никаких походов на python.org, никаких apt install python3.13:
uv python install 3.13
Команда скачивает
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 в таком порядке:
- Если рядом есть файл
.python-version— берёт указанную там версию. - Если в
pyproject.tomlесть полеrequires-python— берёт самую новую совместимую. - Иначе — берёт системную или последнюю установленную.
Поэтому пины делать стоит. В уроке 02 разберём, как это пишется в pyproject.toml. А .python-version создаётся автоматически командой:
uv python pin 3.13
Файл .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:
uvпрочиталpyproject.toml, посмотрел, какие зависимости уже есть.- Обратился в , узнал, какие версииPyPI
requestsсуществуют. - Запустил resolver — выбрал последнюю совместимую с вашим Python 3.13 версию.
- Если
.venv/ещё не было — создал, поставил туда Python 3.13 иrequests. - Записал в
uv.lockточные версии всех пакетов (включая транзитивные зависимости —requestsтянетurllib3,certifi,charset-normalizer,idna). - Обновил
pyproject.toml, добавив"requests"вdependencies.
Один вызов команды затрагивает три файла и кэш.
Удалить пакет:
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/ подтянет изменения.
Не вызывайте pip install внутри проекта с uv. Установленный таким способом пакет не попадёт в pyproject.toml и uv.lock, ваш uv sync его не воспроизведёт. На следующий день вы откроете проект — пакета нет, скрипт не работает.
uv lock — обновить lockfile без установки
uv lock
Только пересчитывает версии и пишет в uv.lock. Полезно в CI, когда нужно проверить, что lockfile в актуальном состоянии, без фактической установки .venv/.
Что делает uv run под капотом
uv run — самая частая команда, которой вы запускаете код. Шаги внутри:
- Найти ближайший вверх по дереву
pyproject.toml. С этой папки начинается «проект». - Если в проекте нет
.venv/или он устарел — пересоздать и засинковать. - Если зависимости в
pyproject.tomlотличаются от установленных — досинкнуть (быстро, инкрементально). - Выставить
PATHтак, чтобыpythonуказывал на.venv/bin/python. - Запустить переданную команду —
python script.py, илиpython -m mypackage, или простоpytest.
Поэтому uv run pytest работает без явной активации окружения:
Это поведение объясняет частую путаницу:
# 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-утилиту разово |
Упражнение
- Установите две версии Python через
uv:
uv python install 3.12 3.13
-
Создайте две папки-песочницы —
proj-oldиproj-new. В первой запиньте Python 3.12, во второй — 3.13. Используйтеuv init+uv python pin. -
В каждой запустите следующий скрипт
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 всё пишет.