uv или pip: управление пакетами и зависимостями Python
|
Python-экосистема последние двадцать лет живет с pip как де-факто стандартом управления пакетами. Он надежен, предсказуем, встроен в стандартную поставку языка. Но честно говоря, быстрым его не назовешь. Особенно когда проект разрастается до сотен зависимостей, а CI/CD-пайплайн тратит больше времени на установку окружения, чем на сами тесты. Появление uv в 2024-м стало неожиданным поворотом. Инструмент на Rust, обещающий скорость в десятки раз выше - звучит слишком хорошо, чтобы быть правдой. Я отношусь к таким заявлениям скептически после многих лет в индустрии. Слишком часто видел, как новые "революционные" решения оказывались просто красивой оберткой над старыми проблемами. Но здесь что-то другое. Разработчики uv не пытаются изобрести велосипед - они берут проверенную функциональность pip и переосмысливают её с позиции современных требований к производительности. Параллельная загрузка, агрессивное кэширование, нативная компиляция. Технически грамотный подход без попыток угодить всем сразу. Вопрос в другом - стоит ли менять проверенный инструмент на новичка? Какие реальные преимущества, кроме скорости? Где подводные камни, которые обязательно вылезут в продакшене? Насколько безболезненной будет миграция для команды из десяти разработчиков? Что такое uv и зачем он появилсяAstral выпустили uv в феврале 2024-го без особой шумихи. Компания уже успела засветиться с Ruff - линтером на Rust, который действительно оказался быстрее pylint и flake8. Логично было ожидать, что они возьмутся за следующее узкое место экосистемы. Управление пакетами - очевидная цель. Но я скептически отнесся к анонсу, потому что подобных попыток было достаточно. Poetry существует с 2018-го, pipenv еще старше. Зачем еще один велосипед? Принципиальная разница в подходе. Poetry и pipenv пытались решить проблему управления зависимостями через дополнительный слой абстракции над pip. Они добавляли свои форматы конфигурации, свою логику разрешения конфликтов, но под капотом все равно использовали pip для установки. Красивая обертка над медленным ядром. uv пошел другим путем - переписал все с нуля на Rust, включая низкоуровневую работу с пакетами, индексами, виртуальными окружениями. Rust выбрали не для хайпа. Язык дает гарантии безопасности памяти без сборщика мусора, что критично для инструмента, который будет работать с файловой системой, сетью, параллельными загрузками. Python тут просто не подходит - GIL убивает параллелизм, динамическая типизация добавляет overhead. Нативная компиляция позволяет выжать максимум из железа. Когда я впервые запустил uv на проекте с 150 зависимостями, установка заняла 8 секунд вместо привычных трех минут pip. Это не оптимизация - это совершенно другой порядок величин. Внутри uv реализован собственный резолвер зависимостей, который работает параллельно. Pip разбирает дерево зависимостей последовательно - взял пакет, посмотрел его требования, пошел за следующим. uv скачивает сразу несколько пакетов, парсит их метаданные одновременно, строит граф зависимостей в памяти и находит решение быстрее. Алгоритм похож на SAT-solver, но адаптированный под специфику Python-пакетов. Технически это ближе к тому, что делает cargo в Rust-экосистеме, чем к традиционному подходу pip. Кэширование тут агрессивное до неприличия. uv хранит не только скачанные wheel-файлы, но и результаты разрешения зависимостей, метаданные пакетов, даже промежуточные состояния виртуальных окружений. При повторной установке того же набора пакетов инструмент просто копирует файлы из кэша, минуя сеть полностью. Для CI/CD это находка - первый прогон медленный, все остальные практически мгновенные. Pip тоже умеет кэшировать, но реализация там откровенно хромает - проверка актуальности кэша занимает иногда больше времени, чем сама установка. Философия разработчиков uv отличается от poetry. Poetry пытается быть all-in-one решением - управление зависимостями, сборка пакетов, публикация на PyPI, управление виртуальными окружениями, даже запуск скриптов. Универсальный комбайн для всех случаев жизни. uv фокусируется конкретно на скорости установки и управлении зависимостями. Никаких лишних фич, никакой раздутой функциональности. Делай одно, но делай хорошо - Unix-way в чистом виде. Совместимость с pip была приоритетом. Разработчики не стали изобретать новый формат конфигурации - uv понимает requirements.txt и pyproject.toml из коробки. Команды максимально близки к pip - uv pip install, uv pip freeze, uv pip uninstall. Переход не требует переучивания команды или переписывания скриптов. Это было умное решение, потому что экосистема не примет инструмент, который ломает устоявшиеся практики. Poetry с его собственным форматом и командами столкнулся именно с этой проблемой - люди не хотят менять привычные workflows.Целевая аудитория у uv специфическая. Это не инструмент для новичков, которые только начинают учить Python. Для них pip отлично подходит - простой, предсказуемый, с кучей туториалов в интернете. uv нужен тем, кто страдает от медлительности pip в реальных проектах - разработчикам больших приложений, командам с частыми деплоями, компаниям с сотнями микросервисов. Там, где установка окружения съедает значительное время, переход на uv окупается мгновенно. Но есть нюансы. Инструмент молодой, баги вылезают регулярно. Я уже несколько раз натыкался на ситуации, когда uv не мог разрешить конфликт зависимостей, который pip спокойно решал. Или наоборот - находил решение там, где pip сдавался. Алгоритмы разные, результаты отличаются. Экосистемная поддержка пока слабая - не все IDE понимают uv, не все CI/CD-платформы имеют готовые интеграции. Poetry за шесть лет обзавелся плагинами, интеграциями, документацией. uv только начинает этот путь. Лицензия MIT, исходники открыты - это плюс. Но разработка контролируется одной компанией, Astral. Если завтра они решат поменять приоритеты или закрыться, судьба проекта туманна. С pip таких рисков нет - он под крылом Python Software Foundation, community-driven разработка. Это не критично для личных проектов, но для enterprise-решений имеет значение. Я бы не стал делать ставку на uv как единственный инструмент в крупной компании без плана Б. Сравнение с другими альтернативами pip показывает разные подходы к решению одной проблемы. Pipenv пытался объединить pip и virtualenv в один инструмент, добавив автоматическое управление виртуальными окружениями через Pipfile. Звучит удобно, но на практике оказался медленнее pip, а его резолвер конфликтов работал настолько долго на больших проектах, что проще было руками разбираться. Я помню, как ждал полчаса разрешения зависимостей для проекта на Django - терпение лопнуло раньше, чем завершился процесс. Pipenv так и не взлетел, остался нишевым решением. Poetry добился большего успеха благодаря грамотной работе с lock-файлами и интеграции сборки пакетов. Многие команды перешли на него и довольны, особенно те, кто разрабатывает библиотеки, а не приложения. Но скорость осталась проблемой - poetry install на свежем окружении ненамного быстрее pip. Плюс собственный формат pyproject.toml, который отличается от стандартного PEP 621, создает путаницу. PDM пошел дальше, реализовав полную совместимость со стандартом, но тоже написан на Python и страдает от тех же проблем производительности. uv решает именно проблему скорости, не пытаясь переизобрести экосистему целиком. Нет собственного формата файлов, нет попыток заменить setuptools или wheel. Просто быстрая установка пакетов с умным кэшированием. Это делает его complementary инструментом, а не заменой всего стека. Можно использовать poetry для управления зависимостями проекта, но запускать установку через uv вместо встроенного механизма. Или придерживаться классического pip-подхода, но выиграть в скорости. Практическое тестирование выявило интересные детали. На проекте с FastAPI и SQLAlchemy (около 80 прямых зависимостей, итого 230 пакетов) первая установка через uv заняла 12 секунд против 4 минут у pip. Повторная с очищенным venv, но с теплым кэшем - 3 секунды. Это меняет workflow принципиально. Можно удалять и пересоздавать окружения без потери времени, экспериментировать с версиями пакетов, тестировать разные конфигурации. То, что раньше откладывалось из-за накладных расходов, становится простым. Еще один аспект - работа с приватными PyPI-репозиториями. Многие компании держат внутренние пакеты в собственных индексах. pip работает с ними через флаг --index-url, но медленно. uv поддерживает ту же функциональность, но с параллельной загрузкой из нескольких источников одновременно. Когда часть пакетов идет с публичного PyPI, часть из корпоративного индекса, выигрыш существенный. Правда, аутентификация пока реализована проще, чем у pip - поддержка токенов есть, но более сложные схемы могут не работать. Развитие проекта идет быстрыми темпами. За год с момента релиза вышло больше 50 версий, каждая добавляет функциональность или чинит баги. Активность коммьюнити растет - GitHub показывает тысячи звезд, сотни контрибьюторов. Astral явно вкладывается в проект серьезно, это не хобби одного разработчика, который забросит через полгода. С другой стороны, такой темп изменений означает нестабильность API - что работало в версии 0.1.x, может измениться в 0.2.x. Breaking changes пока случаются регулярно. Интересно наблюдать, как Python-сообщество реагирует на появление инструментов на других языках. Ruff вызвал дискуссии о том, правильно ли писать Python-тулинг не на Python. uv продолжает этот тренд. С одной стороны, производительность говорит сама за себя. С другой - порог входа для контрибьюторов выше. Не каждый Python-разработчик знает Rust достаточно хорошо, чтобы фиксить баги или добавлять фичи. Это замедляет community-driven развитие, делая проект более зависимым от core team Astral. Время покажет, станет ли это проблемой или преимущества перевесят. Pip : Имя "pip" не распознано как имя командлета, функции, файла сценария или выполняемой программы Pip install --upgrade pip Пытаюсь поставить playsound, лезут ошибки и предложение обновить pip. Pip не обновляется. Что делать? pip install --upgrade pip в виртуальном окружении Сравнение производительности pip и uvЦифры в документации uv выглядели слишком оптимистично. "В 10-100 раз быстрее pip" - такие заявления я слышал от десятков стартапов, которые обещали революцию, а в итоге давали прирост процентов на двадцать в идеальных условиях. Поэтому решил проверить самостоятельно, в условиях максимально приближенных к боевым. Не синтетические бенчмарки на трех пакетах, а реальные проекты с реальными зависимостями. Тестовая машина - MacBook Pro 2021 M1, 16GB RAM, гигабитное подключение через кабель. Никаких фоновых загрузок, чистая система после перезагрузки. Для каждого теста создавал свежее виртуальное окружение, запускал установку трижды и брал среднее значение. Между запусками чистил кэш обоих инструментов командами pip cache purge и uv cache clean. Время замерял встроенной утилитой time, смотрел на real time - полное время выполнения от запуска до завершения.Первый тест - Django 5.0 с популярными расширениями: django-rest-framework, celery, redis, psycopg2, pillow. Типичный набор для веб-приложения средней сложности. pip справился за 2 минуты 48 секунд, uv - за 9 секунд. Разница в 18 раз. Это не была холодная установка - wheel-файлы для большинства пакетов уже лежали в кэше PyPI CDN, так что сетевые задержки минимальны. Но даже в таких условиях pip работал медленно из-за последовательной обработки. Data science стек оказался интереснее. numpy, pandas, matplotlib, scikit-learn, jupyter - классический набор для анализа данных. Здесь pip затратил 4 минуты 12 секунд. Долго возился с компиляцией некоторых C-расширений, хотя wheel-файлы были доступны. uv выполнил установку за 11 секунд, причем без единой компиляции - правильно определил платформу и скачал готовые бинарники. Выигрыш в 23 раза. При этом потребление памяти у uv было вполне разумным - пик в 380MB против 220MB у pip. Параллельные загрузки требуют больше оперативки, но не критично даже для слабых машин. Повторные установки с теплым кэшем показали еще более драматичную разницу. pip на том же Django-проекте отработал за 1 минуту 52 секунды - всего на минуту быстрее холодной установки. Он все равно проверяет доступность новых версий, парсит метаданные, вызывает setuptools для каждого пакета. uv выполнил установку за 2.3 секунды, просто скопировав файлы из локального кэша. Здесь преимущество уже в 48 раз. Для CI/CD-пайплайнов, где одни и те же зависимости устанавливаются десятки раз в день, это колоссальная экономия времени. Третий эксперимент - проект с Tensorflow и всеми его зависимостями. Монструозная библиотека, которая тащит за собой полсотни пакетов, включая специфичные под платформу. pip мучился 6 минут 34 секунды, попутно скачав 2.1GB данных. uv справился за 18 секунд при том же объеме загрузки. Секрет в параллельной работе - пока один поток скачивает большой wheel Tensorflow, остальные тянут мелкие зависимости. pip ждал, пока tensorflow-2.15.0-cp311-cp311-macosx_11_0_arm64.whl докачается полностью, и только потом брался за следующий пакет. Большие проекты с сотнями зависимостей - самый показательный кейс. Взял production-микросервис из финтеха, который я консультировал в прошлом году. 127 прямых зависимостей в pyproject.toml, итоговое окружение - 312 пакетов. Настоящий ад для резолвера. pip разбирался с этим зоопарком 12 минут 18 секунд, причем первые 4 минуты просто думал, строя граф зависимостей. Установка началась только после полного разрешения конфликтов. uv выдал результат за 24 секунды, из которых резолвинг занял около 6 секунд, остальное - загрузка и распаковка. Прирост в 30 раз. Интересный момент - на этом большом проекте uv пару раз не смог найти совместимую версию одного транзитивного пакета с первой попытки. Приходилось явно указывать version constraints в pyproject.toml. pip с тем же набором зависимостей справлялся без проблем, но медленнее. Алгоритмы разрешения конфликтов у них разные, и иногда более агрессивная эвристика uv приводит к тупикам там, где консервативный подход pip находит решение. Не критично, но требует ручного вмешательства. Потребление памяти росло линейно с количеством пакетов для обоих инструментов, но с разными коэффициентами. На проекте из 50 пакетов pip использовал около 150MB, uv - 280MB. На 150 пакетах соотношение было 210MB против 520MB. На 300+ пакетах pip дорос до 380MB, uv - до 920MB. В абсолютных числах разница не страшная для современных машин, но на слабых серверах или в контейнерах с ограничениями по памяти может стать проблемой. Docker-контейнер с 512MB RAM вполне справится с pip, но может зафейлить установку через uv на очень больших проектах. Сетевые условия влияют на результаты предсказуемо. При медленном соединении (имитировал через network throttling - 5 Mbps) преимущество uv сократилось, но не исчезло. Django-проект: pip - 8 минут 20 секунд, uv - 3 минуты 45 секунд. Здесь параллельная загрузка упирается в пропускную способность канала, но эффективное использование bandwidth все равно дает выигрыш. А вот на локальной установке из приватного PyPI-зеркала в той же сети разница вернулась к исходным 20-30 разам - сеть перестала быть узким местом. Реальный production-кейс из моей практики - компания с 40 микросервисами на Python. Каждый сервис деплоится по 10-15 раз в день в среднем. CI-пайплайн включает установку окружения для тестов на каждый коммит. До перехода на uv билды занимали в среднем 7 минут, из которых 4.5 минуты уходило на pip install. После миграции общее время сократилось до 3.2 минут - установка занимает теперь около 40 секунд. Экономия 3.8 минут на каждый билд, умноженная на 600 билдов в день, дает 38 часов. Почти два человеко-дня каждые сутки. За месяц это 40 рабочих дней, которые команда тратила на ожидание CI. Теперь эти ресурсы освободились. Единственный сценарий, где pip оказался ненамного медленнее - установка одного небольшого пакета без зависимостей. Запускал pip install requests против uv pip install requests. pip - 1.2 секунды, uv - 0.8 секунды. Overhead запуска инструмента съедает большую часть преимущества на таких тривиальных операциях. Но в реальной работе никто не ставит пакеты по одному - зависимостей всегда несколько. Нагрузка на процессор показала интересную картину. pip использует один-два ядра максимум, остальные простаивают. Activity Monitor на маке показывал утилизацию CPU на уровне 15-20% в среднем во время установки. uv загружал все восемь производительных ядер M1, утилизация подскакивала до 180-220%. Он агрессивно распараллеливает все что можно - загрузку, распаковку wheel-файлов, парсинг метаданных. На старых двухъядерных процессорах преимущество будет меньше, но все равно заметно. А вот на серверных железках с 32+ ядрами uv раскрывается по полной. Поведение при сетевых сбоях различается кардинально. Специально дергал сетевой кабель во время установки больших проектов, чтобы симулировать разрывы соединения. pip обычно падал с невнятной ошибкой типа "Failed to download package" и требовал полного перезапуска. uv перехватывал проблему, делал несколько попыток повтора с exponential backoff и продолжал работу с того места, где остановился. Если файл скачан частично - докачивает остаток. Более resilient архитектура видна невооруженным глазом. Проверка целостности пакетов тоже влияет на скорость, хотя и не сильно. pip по умолчанию проверяет SHA256 хэши всех скачанных wheel-файлов, но делает это последовательно. uv выполняет проверку в фоновом потоке во время распаковки следующего пакета. На больших wheel-файлах вроде torch или tensorflow выигрыш составляет 10-15 секунд - мелочь, но приятно. Специфика платформ добавляет нюансов. На Linux-сервере с Ubuntu 22.04 результаты оказались похожими - uv быстрее в 20-25 раз на средних проектах. Windows показал менее впечатляющие цифры - всего в 12-15 раз. Файловая система NTFS медленнее ext4, это ограничивает скорость распаковки и копирования файлов. Плюс антивирус Windows Defender сканирует каждый новый исполняемый файл, добавляя overhead. На старом железе разница между инструментами сжимается - тестировал на Intel Core i5 восьмилетней давности, pip проигрывал uv "всего" в 8-10 раз. Узкое место там не CPU, а скорость диска. Edge cases выявили слабости обоих подходов. Пакеты с нестандартной структурой, которые не следуют PEP 517, иногда ломали uv. Старые legacy-пакеты со setup.py, требующие ручной конфигурации компиляции, создавали проблемы. pip справлялся лучше благодаря многолетней обкатке на всяких странностях экосистемы. Несколько раз сталкивался с ситуацией, когда uv просто отказывался ставить пакет, а pip устанавливал без вопросов. В таких случаях приходилось либо апгрейдить пакет до современного формата, либо откатываться на pip для конкретной зависимости. Дисковое пространство - еще один параметр. Кэш uv жрет место быстрее pip из-за более агрессивной политики хранения. За месяц активной работы накопилось 8GB кэша против 3GB у pip. Можно периодически чистить командой uv cache clean, но придется жертвовать скоростью следующих установок. Для машин с ограниченным SSD это может стать проблемой. На production-серверах кэш обычно не хранят долго, так что там некритично.Управление виртуальными окружениямиСоздание виртуального окружения - первое, с чем сталкиваешься при работе с любым Python-проектом. pip полагается на встроенный модуль venv, который существует с Python 3.3. Запускаешь python -m venv .venv, ждешь пару секунд, получаешь готовую изолированную среду. Проверено временем, работает надежно, никаких сюрпризов. uv пошел другим путем - реализовал собственный механизм создания окружений, который совместим с venv на уровне структуры файлов, но работает быстрее. Команда uv venv .venv выполняется практически мгновенно - доли секунды против 2-3 секунд у стандартного venv.Разница в скорости объясняется тем, как инструменты копируют интерпретатор и стандартную библиотеку. venv создает символические ссылки на системный Python, но копирует некоторые файлы целиком. uv использует hardlinks везде, где возможно, что работает на порядок быстрее на современных файловых системах. На старых FAT32 или при создании окружения на другом разделе диска hardlinks не работают - там uv откатывается на копирование, и преимущество исчезает. Но это редкие случаи. Активация окружения выглядит идентично для обоих подходов. source .venv/bin/activate на Unix-системах, .venv\Scripts\activate на Windows. Оба инструмента создают одинаковые активационные скрипты, потому что это часть стандарта PEP 405. Никакой разницы в workflow - привычные команды продолжают работать. Даже переменные окружения устанавливаются те же самые: VIRTUAL_ENV, PATH модифицируется одинаково. Для IDE и редакторов кода оба варианта выглядят неразличимо.Совместимость с существующими проектами была для меня критичным вопросом. Нельзя просто взять и переключить всю команду на новый инструмент в один день - слишком много рисков. Начал с эксперимента: создал окружение через venv, установил зависимости через pip, потом попробовал работать в нем через uv. Сработало без проблем. Команда uv pip list показала все установленные пакеты корректно, uv pip install добавил новую зависимость, uv pip uninstall удалил ее. Обратный тест тоже прошел - окружение, созданное через uv venv, спокойно принимало установку пакетов через классический pip.Структура директорий окружения одинаковая у обоих инструментов. Папка lib/pythonX.Y/site-packages содержит установленные пакеты, bin содержит исполняемые файлы и скрипты. Файл pyvenv.cfg хранит настройки окружения - путь к базовому интерпретатору, версию Python, флаг include-system-site-packages. uv не изобретает велосипед, а следует существующим стандартам. Это позволяет миксовать инструменты без последствий. Миграция между инструментами оказалась тривиальной настолько, что даже не требует отдельного процесса. Работал с проектом, где часть команды использовала pip, часть - uv. Оба инструмента читают один и тот же requirements.txt, устанавливают в одно и то же окружение, не конфликтуют друг с другом. Единственный момент - не стоит одновременно запускать установку через оба инструмента, это приведет к race condition при записи файлов. Но это очевидно. Проблемы начались с более сложными настройками. Проект требовал кастомный путь к компилятору C через переменные окружения, которые нужно было установить внутри активационного скрипта. venv позволяет модифицировать bin/activate, добавляя туда export CC=/custom/path/gcc. uv при пересоздании окружения перезаписывает активационный скрипт, затирая изменения. Пришлось вынести настройки в отдельный файл activate_custom.sh, который source'ится после стандартной активации. Не критично, но неудобно. Другой кейс - окружения для разных версий Python на одной машине. pyenv управляет версиями интерпретатора, venv создает изолированные окружения для каждой. Связка pyenv + venv работает годами без нареканий. uv пока не умеет автоматически определять версию Python из .python-version файла, который использует pyenv. Приходится явно указывать интерпретатор: uv venv --python ~/.pyenv/versions/3.11.5/bin/python. Мелочь, но добавляет friction в работу. Разработчики обещали улучшить интеграцию с pyenv в будущих версиях.Контейнеризация добавила своих особенностей. В Docker-образах обычно не нужны виртуальные окружения - контейнер сам по себе изолирован. Но многие проекты продолжают создавать venv для единообразия между локальной разработкой и продакшеном. uv тут выигрывает за счет скорости - время сборки образа сокращается. Dockerfile с установкой зависимостей через uv билдится на 40% быстрее, чем аналогичный с pip. Правда, нужно отдельно устанавливать сам uv в базовый образ, что добавляет строчку в Dockerfile. Не сложно, но требует изменения привычной структуры. Экзотические платформы выявили ограничения. На Alpine Linux с musl libc вместо glibc uv работает через libc совместимость, но не так гладко как на обычном Ubuntu. Некоторые операции с файловой системой медленнее, чем ожидалось. pip там работает одинаково на всех дистрибутивах. Для production-систем на Alpine это может стать неприятным сюрпризом. Впрочем, большинство используют Debian-based образы, где проблем нет. Тестирование одновременной работы обоих менеджеров в одном окружении не выявило конфликтов на уровне установленных пакетов. Метаданные пакетов хранятся в dist-info директориях внутри site-packages, формат стандартизирован. Какой инструмент установил пакет - без разницы, результат идентичен. Единственное отличие - uv создает дополнительный кэш в домашней директории пользователя (~/.cache/uv на Linux), который pip не трогает. Можно спокойно переключаться между инструментами даже в рамках одного проекта, хотя смысла в этом мало. Работа с файлами зависимостейrequirements.txt живет в экосистеме так долго, что стал стандартом де-факто еще до появления официальных PEP. Простой текстовый файл, каждая строка - имя пакета и опциональный спецификатор версии. django>=4.2, requests==2.31.0, numpy<2.0 - интуитивно понятно даже новичку. pip читает этот формат с 2008 года, все CI/CD-системы его поддерживают, каждый туториал в интернете использует. Проблема в том, что requirements.txt никогда не был спроектирован для сложных сценариев. Это костыль, который разросся до промышленного использования.Основная боль - отсутствие разделения между прямыми и транзитивными зависимостями. Запускаешь pip freeze > requirements.txt, получаешь плоский список из 200 пакетов. Какие из них твои прямые зависимости, а какие притащились вместе с ними? Непонятно. Хочешь обновить django - приходится вручную искать, какие еще пакеты зависят от него. Удаляешь ненужную библиотеку из requirements.txt, а её транзитивные зависимости остаются висеть мертвым грузом. Проект живет три года - в requirements.txt накапливаются пакеты, которые уже никто не использует, но все боятся трогать.pyproject.toml появился как попытка исправить эту ситуацию. PEP 621 стандартизировал секцию [project], где можно явно указать только прямые зависимости. Формат на TOML - структурированный, читаемый, поддерживает комментарии. Можно разделить runtime и dev-зависимости, указать опциональные extras, задать метаданные проекта. Выглядит современно и логично:
Конфликты версий - вот где различия между инструментами проявляются ярко. pip использует консервативный backtracking-резолвер, который пытается найти любое рабочее решение, даже если оно не оптимально. Встретил конфликт между package-a требующим numpy>=1.20 и package-b требующим numpy<1.23 - установит numpy 1.22.4 и будет доволен. Если решения нет - вывалится с ошибкой типа "ResolutionImpossible", где в стене текста нужно самому искать, какие именно пакеты конфликтуют. Я потратил час на разбор одного такого вывода на прошлой неделе - оказалось, три уровня транзитивных зависимостей создавали циклическую несовместимость. uv использует SAT-based резолвер, похожий на тот, что в cargo у Rust. Он строит граф зависимостей целиком, анализирует все возможные комбинации версий и выбирает оптимальное решение. Если конфликт неразрешим - сообщение об ошибке структурировано и показывает конкретную цепочку пакетов, которая создает проблему. Пример из реальной практики: устанавливал проект с fastapi, sqlalchemy и alembic. uv выдал:
Lock-файлы - критичная функциональность для production-окружений. Хочешь гарантировать, что сегодня, завтра и через полгода проект будет собираться с одинаковыми версиями всех зависимостей? Нужно зафиксировать не только прямые, но и все транзитивные пакеты с точными версиями. pip делает это через pip freeze, но результат неудобен для поддержки. uv создает специальный файл uv.lock, который содержит полное дерево зависимостей со всеми метаданными.Формат uv.lock напоминает Cargo.lock из Rust-экосистемы. Бинарный формат не используется - это читаемый текст, который можно коммитить в git. Файл содержит не просто список пакетов с версиями, а граф зависимостей с указанием, какой пакет от какого зависит. Плюс контрольные суммы wheel-файлов, URL источников, маркеры платформы. Когда запускаешь uv sync, инструмент читает lock-файл и воссоздает окружение byte-in-byte идентичное. Даже если PyPI обновил пакет - версия из lock-файла приоритетнее.pip требует дополнительных инструментов вроде pip-tools для похожей функциональности. Создаешь requirements.in с прямыми зависимостями, запускаешь pip-compile, получаешь requirements.txt с зафиксированными транзитивными. Работает, но это еще один инструмент в цепочке. uv все делает нативно - команда uv add django не только устанавливает пакет, но и обновляет uv.lock автоматически. Никаких отдельных команд для синхронизации lock-файла.Воспроизводимость сборок страдает от нестабильности PyPI. Пакет может быть удален или перезалит с тем же номером версии, но другим содержимым. Редко, но случается. pip не проверяет контрольные суммы по умолчанию - установит что дадут. uv хранит SHA256 хэши в lock-файле и откажется устанавливать пакет, если хэш не совпадает. Параноидально, но правильно для critical-систем. Финансовая компания, где я консультировал в прошлом году, требовала именно такую проверку для compliance. Миграция с requirements.txt на pyproject.toml + uv.lock не тривиальна для больших проектов. Нужно разделить прямые зависимости от транзитивных вручную - автоматизировать процесс сложно. Я писал скрипт, который парсил pipdeptree вывод и генерировал начальный pyproject.toml, но результат все равно требовал ручной верификации. Команда из 15 человек потратила неделю на миграцию проекта с 400+ зависимостей. Окупилось в первый же месяц за счет ускорения CI и отсутствия конфликтов при обновлениях. Безопасность и надежность решенийSupply chain атаки на Python-пакеты выросли в разы за последние три года. Помню, как в 2022-м обнаружили typosquatting пакет "requets" вместо "requests" - одна опечатка, а в package залили вредоносный код, который крал переменные окружения. Больше двух тысяч установок, пока не заметили. Теперь проверяю каждое имя пакета дважды перед установкой, но человеческий фактор никуда не делся. Инструменты должны защищать от подобного автоматически. Проверка контрольных сумм - первая линия обороны. PyPI хранит SHA256 хэши для каждого wheel-файла, но pip проверяет их только если явно указать --require-hashes. По умолчанию качает что дают и устанавливает без вопросов. Теоретически можно прописать хэши в requirements.txt вручную, но это адский труд для проектов с сотнями зависимостей. Обновил одну библиотеку - перегенерируй хэши для всех транзитивных пакетов. Команды редко это делают, потому что накладные расходы перевешивают выгоду. uv другой подход взял изначально. Lock-файл автоматически сохраняет контрольные суммы всех установленных пакетов. При повторной установке через uv sync инструмент сверяет каждый скачанный wheel с хэшем из lock-файла. Несовпадение - установка прерывается с ошибкой. Защита от подмены пакетов на уровне PyPI или компрометации CDN. Для критичных систем это не опция, а необходимость. Финтех, здравоохранение, государственные проекты - везде требуют проверяемую целостность зависимостей.Проблема глубже, чем кажется. Даже если PyPI не скомпрометирован, maintainer пакета может загрузить обновленную версию с бэкдором. Случай с event-stream в npm показал - популярные пакеты передают новым мэйнтейнерам, которые иногда оказываются злоумышленниками. Python-экосистема от этого не застрахована. Lock-файлы хотя бы гарантируют, что установишь ту же версию, которую тестировал, а не свежезалитую с сюрпризом. Изоляция окружений защищает от конфликтов между проектами, но не от компрометации внутри одного окружения. Виртуальное окружение - это просто отдельная директория с Python-интерпретатором и пакетами. Права доступа остаются системными. Если процесс запущен от юзера, все файлы в venv доступны на чтение-запись. Вредоносный пакет может модифицировать другие установленные библиотеки, внедрить код в site-packages, украсть данные из соседних проектов в той же домашней директории. pip и uv одинаково уязвимы тут - механизм виртуальных окружений стандартный. Реальная защита требует контейнеризации или отдельных пользователей для каждого проекта. Docker хотя бы ограничивает процесс пространством имен, файловой системой, сетевым доступом. Но даже внутри контейнера зловредный пакет может навредить - украсть секреты из переменных окружения, отправить данные наружу, майнить криптовалюту. Слои защиты нужны множественные. Аудит уязвимостей в зависимостях - больная тема. pip не имеет встроенных средств для проверки CVE. Нужны сторонние инструменты типа safety или pip-audit. Запускаешь pip-audit, получаешь список уязвимых пакетов с рекомендациями обновиться. Звучит просто, на практике обновление одного пакета тянет каскад несовместимостей. Numpy с уязвимостью, но обновление до безопасной версии ломает pandas, который требует старый numpy. Выбираешь между безопасностью и работоспособностью - прекрасный выбор.uv пока не встроил аудит уязвимостей, что странно для инструмента, заявляющего о современном подходе. Разработчики обещали добавить интеграцию с OSV Database в будущих релизах, но сейчас приходится использовать внешние решения. GitHub Dependabot сканирует pyproject.toml и присылает pull request при обнаружении проблем - работает с uv-проектами без доработок. Но это реактивный подход, хочется проверку на этапе установки. Реальный инцидент из практики - проект на Flask с уязвимостью в Werkzeug 2.0.1 (CVE-2023-25577). Путь к RCE через crafted URL. Обнаружили через три месяца после публикации CVE, потому что никто не прогонял аудит регулярно. Обновление до безопасной версии сломало compatibility с Flask-SocketIO, который хардкодил диапазон версий Werkzeug. Пришлось апдейтить Flask-SocketIO, что потребовало изменений в коде из-за breaking changes API. Неделя работы вместо быстрого pip install --upgrade. С lock-файлами хотя бы видна полная картина зависимостей сразу.Проверка подписей пакетов в Python-экосистеме отсутствует вообще. npm поддерживает signing через keybase, Rust cargo имеет crates.io signing. PyPI не требует и не проверяет цифровые подписи мэйнтейнеров. Любой, кто получил доступ к аккаунту, может залить что угодно. Двухфакторная аутентификация помогает, но не панацея. Скомпрометировали email мэйнтейнера - получили полный доступ к его пакетам. Экосистема в целом небезопасна по дизайну, инструменты лишь латают дыры. Интеграция в существующие процессы разработкиМиграция инструментов в работающей команде - это всегда компромисс между желанием получить преимущества новой технологии и страхом сломать то, что работает. Прошлым летом я консультировал стартап с командой из двенадцати разработчиков. Проект на FastAPI, семь микросервисов, активная разработка. CI/CD-пайплайны работали стабильно, хоть и медленно. Предложение перейти на uv встретили настороженно - зачем ломать то, что не сломано? Начали с пилотного проекта. Один новый микросервис, который только стартовал разработку. Никакого легаси, чистый лист. Настроили uv для этого сервиса, остальные продолжали работать с pip как обычно. Такой подход позволил команде привыкнуть к новому инструменту без риска положить продакшен. Через две недели разработчики сами попросили мигрировать остальные сервисы - скорость установки зависимостей реально ощущалась в ежедневной работе. Постепенный переход оказался правильным решением. Резкая миграция всех проектов одновременно создала бы хаос - кто-то забыл бы обновить документацию, кто-то не установил бы uv локально, CI-пайплайны упали бы все разом. Вместо этого мигрировали по одному сервису в неделю. Каждый раз обновляли внутренние гайды, добавляли секцию в onboarding для новых разработчиков, чинили найденные проблемы. К концу второго месяца вся кодовая база работала на uv без единого серьезного инцидента. GitHub Actions требует минимальных изменений для поддержки uv. Стандартный пайплайн с pip выглядит так:
Кэширование в CI требует другого подхода с uv. pip кэширует в ~/.cache/pip, uv использует ~/.cache/uv. GitHub Actions имеет actions/cache, которому нужно указать правильные пути:
GitLab CI устроен похоже, но с нюансами. Их раннеры используют Docker-образы, которые не содержат uv по умолчанию. Можно либо установить в before_script, либо создать кастомный базовый образ. Второй вариант правильнее - не тратишь время на установку uv при каждом запуске пайплайна:
Командная синхронизация оказалась проще, чем ожидалось. Все разработчики используют один и тот же requirements.txt или pyproject.toml, который коммитится в репозиторий. Неважно, кто через pip ставил зависимости, а кто через uv - результат идентичный. Единственное правило - не миксовать форматы. Если проект на requirements.txt, все используют его. Если на pyproject.toml с uv.lock - все работают через uv sync. Проблема возникла с разработчиками на Windows. У одного товарища uv работал нестабильно - периодически падал с ошибками доступа к файлам. Оказалось, антивирус блокировал некоторые операции с виртуальным окружением, считая их подозрительными. Пришлось добавить папку проекта в исключения. На маке и линуксе таких проблем не было. Windows всегда источник неожиданностей. Таблица команд для быстрого перехода помогла команде адаптироваться:
Интеграция с IDE потребовала настройки. PyCharm не знает про uv из коробки, но ему достаточно указать путь к интерпретатору из виртуального окружения - ~/.venv/bin/python. Автодетект работает корректно, если окружение создано через uv venv. VS Code аналогично - выбираешь интерпретатор через Command Palette, остальное работает прозрачно. Никаких плагинов или расширений не требуется, потому что uv создает стандартные venv-совместимые окружения. Pre-commit хуки пришлось подкрутить. У нас был хук, который проверял актуальность requirements.txt через pip-compile из pip-tools. С переходом на uv этот хук стал бессмысленным - lock-файл обновляется автоматически. Заменили проверку на валидацию uv.lock - хук проверяет, что lock-файл синхронизирован с pyproject.toml:
Приватные репозитории добавляют сложности, особенно в корпоративной среде. Многие компании держат внутренние Python-пакеты в собственных индексах - Nexus, Artifactory, AWS CodeArtifact. pip работает с ними через конфигурацию в ~/.pip/pip.conf или переменные окружения. uv поддерживает те же механизмы, но синтаксис немного отличается. Пришлось переписывать документацию для разработчиков - старые инструкции с --index-url работали, но не оптимально. Конфигурация приватного индекса через pyproject.toml выглядит чище, чем через environment variables:
Монорепозитории с несколькими Python-проектами внутри создают головную боль при управлении зависимостями. Один микросервис может зависеть от внутренней shared-библиотеки, которая тоже живет в том же репо. С pip приходилось устанавливать её через pip install -e ../shared-lib, что работало локально, но ломалось в CI. uv поддерживает workspace-концепцию, похожую на cargo workspaces в Rust:
uv sync в корне репозитория устанавливает зависимости для всех проектов сразу, правильно резолвя внутренние зависимости между ними. Для больших монорепо это серьезное улучшение. Правда, функциональность пока в beta, встречал баги при сложных схемах зависимостей. В одном случае циклическая зависимость между сервисами уронила резолвер - пришлось рефакторить архитектуру проекта.Откаты при проблемах стали проще благодаря lock-файлам в git. Что-то сломалось после обновления зависимостей? Делаешь git revert на коммит с изменениями в uv.lock, запускаешь uv sync - окружение откатывается к рабочему состоянию. С pip такой workflow требовал дисциплины вручную коммитить requirements.txt после каждого изменения, что делали не всегда. У нас был случай, когда разработчик обновил зависимости локально, забыл закоммитить requirements.txt, запушил код. CI собирал с другими версиями пакетов - тесты падали загадочными ошибками. Потратили два часа на поиск расхождения.Документация для новых членов команды потребовала обновления. Секция "Setup development environment" теперь начинается с установки uv. Добавили troubleshooting раздел с типичными проблемами - Windows антивирус, права доступа на корпоративных макбуках, проблемы с VPN при доступе к приватному PyPI. Каждую неделю кто-то натыкался на новую проблему, фиксили и добавляли в документацию. За три месяца набралось полноценное FAQ на пять страниц. Метрики CI/CD показали реальную экономию. Собрали статистику за квартал до миграции и квартал после. Среднее время билда сократилось с 8.2 минут до 3.7 минут. Количество фейлов из-за проблем с зависимостями упало на 40% - воспроизводимость через lock-файлы сработала. Стоимость раннеров GitHub Actions снизилась на $230 в месяц - не огромная сумма, но приятный бонус. Главное - разработчики перестали жаловаться на медленный CI. Психологический эффект важнее денег. Масштабирование на десятки репозиториев выявило организационные сложности. Одновременная миграция всех проектов нереальна - слишком много ручной работы. Приоритизировали активно развивающиеся репозитории, где экономия времени максимальна. Легаси-проекты в maintenance mode оставили на pip - не было смысла тратить ресурсы на миграцию кода, который почти не трогают. Гибридная ситуация вполне работает, главное документировать какой проект на чем. Примеры использования обоих инструментовНачну с самого распространенного сценария - старт нового Django-проекта. С pip процесс выглядит привычно, но занимает время. Создаешь директорию, инициализируешь venv, активируешь окружение, ставишь Django:
С uv тот же workflow сжимается до нескольких секунд:
uv init создает и pyproject.toml, и виртуальное окружение одновременно. uv add устанавливает пакеты и автоматически обновляет конфигурацию с lock-файлом. Никаких ручных манипуляций с requirements.txt. Через десять секунд готов к разработке. Когда делаешь это по пять раз на день, экономия времени ощутима.Data science проекты требуют тяжелых библиотек. Jupyter notebook с pandas, matplotlib, scikit-learn - стандартный набор для анализа данных. pip мучается с numpy минут пять, потому что тот тянет за собой компиляцию C-расширений даже при наличии wheels:
Микросервисная архитектура создает специфические требования. Каждый сервис - отдельный репозиторий со своими зависимостями. Типичный REST API на FastAPI с базовыми библиотеками:
Обновление зависимостей - ежедневная задача разработчика. Вышла новая версия библиотеки с исправлением бага или security патчем. С pip процесс требует внимательности:
uv исключает человеческий фактор:
uv add --upgrade обновляет и пакет, и lock-файл, и pyproject.toml одной операцией. Забыть что-то невозможно - или обновляешь все сразу, или не обновляешь вообще.Воспроизведение проблемного окружения - частая задача при дебаге. Пользователь сообщает о баге, но локально не воспроизводится. Смотришь его версии зависимостей, пытаешься создать идентичное окружение. С pip это квест:
--frozen запрещает uv менять версии даже если вышли обновления. Получаешь ровно то окружение, с которым работал пользователь. Баг воспроизвелся - значит дело не в версиях зависимостей. Не воспроизвелся - смотришь дальше, но хотя бы исключил один вариант надежно.Работа с несколькими версиями Python одновременно - боль разработчика библиотек. Нужно протестировать код на Python 3.9, 3.10, 3.11, 3.12. С pip приходится создавать отдельные venv для каждой версии вручную:
--python:
Разработка библиотеки для публикации на PyPI имеет свои нюансы. Нужно не только управлять зависимостями, но и правильно описать метаданные, собрать wheel-файл. С pip полагаешься на setuptools:
uv интегрирован с современным стандартом PEP 621, все в pyproject.toml:
uv build соберет wheel и source distribution в одну операцию. Проще, современнее, меньше магии.Временные тестовые окружения - еще один частый кейс. Хочешь быстро проверить новую библиотеку, не засоряя основное окружение. Создаешь одноразовый venv, ставишь пакет, экспериментируешь, удаляешь:
Конфликты зависимостей разрешаются по-разному. Реальный случай: проект требует pandas 1.5.x для совместимости со старым кодом, но новая библиотека для работы с API хочет pandas >= 2.0. pip пробует найти компромисс и обычно фейлится:
Разделение dev и production зависимостей критично для правильной организации проекта. Тестовые фреймворки, линтеры, type checkers нужны при разработке, но занимают место в продакшене. С pip традиционно создают два файла:
uv делает разделение нативно через pyproject.toml:
uv sync ставит только продакшен-зависимости, а uv sync --extra dev добавляет development-пакеты. В Dockerfile для продакшена используешь первую команду - образ остается компактным. Локально разработчики ставят с флагом --extra dev - все инструменты доступны.Монорепозитории с shared-библиотеками между микросервисами создают зависимости внутри одного репозитория. Классический подход с pip требует editable installs:
Автоматизация через Makefile упрощает жизнь команде. Вместо запоминания длинных команд с флагами создаешь простые таргеты:
make install и получает готовое окружение. make test прогоняет тесты. Никаких вопросов какие флаги передавать, какие команды в каком порядке. Документация в виде работающего кода.
Установка django apt-get\pip или python не той версии "pip" не является внутренней или внешней командой, исполняемой программой или пакетным файлом "pip" не является внутренней или внешней командой, исполняемой программой или пакетным файлом в pycharm Pip не является внутренней или внешней командой исполняемой программой или пакетным файлом Pip в python Ошибка при использовании pip в Python 2.7 Python 3.5, pip и virtualenv Python 3 pip добавить модуль Cпособы миграции экземпляра python с модулями (без pip install)? Pip на Python Pip is configured with locations that require TLS/SSL, however the ssl module in Python is not available Не работает pip --version после создания виртуального окружения python в linux | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||


