$ sudo teach|
    $ sudo teach|
    IT school
  • Telegram
  • Партнёрам
  • Все курсы
$ sudo teach IT
OOO "SALEPROFIT"Контакты и реквизитыIT-Park Logo

Школа

  • Блог
  • Проверить сертификат

Сотрудничество

  • Стать учителем
  • Партнёрская программа
  • О проекте

Право

  • Оферта
  • Политика конфиденциальности

© 2023–2026 $ sudo teach IT™. All Rights Reserved. Public user contributions licensed under CC BY-SA 4.0 license with attribution required
TelegramGitHubYouTube
ГлавнаяБлогКак устроены зависимости в Python: venv, pip-tools и контроль повторяемости окружений

Как устроены зависимости в Python: venv, pip-tools и контроль повторяемости окружений

$ sudo teach IT
·23 июля 2026 г.·10 мин·53
Как устроены зависимости в Python: venv, pip-tools и контроль повторяемости окружений

Разберём, почему «работает у меня» возникает из-за различий окружений, и как добиться воспроизводимости: lock-файлы, правильная установка и типичные ошибки с версиями.

Содержание
Почему «работает у меня» — это почти всегда про окружениеУровень 1: venv и что он реально гарантируетЧто venv гарантируетЧего venv не гарантируетМинимальный рабочий сценарийУровень 2: pip, requirements и главная ловушкаpip и “разрешение зависимостей”Воспроизводимость: что нужно зафиксироватьpip-tools: зачем он нуженКлючевая идеяНастройка: требования, файлы .in и .txt1) Создаём requirements.in2) Генерируем lock: requirements.txtУстановка из lock-файла: стабильность вместо догадокОбновление зависимостей без потери контроляПример обновленияКонтроль воспроизводимости по Python-версииПрактический подходРазделение окружений: prod, dev, тестыПример генерации dev lockУстановкаВажные опции pip-compile: что влияет на повторяемостьВыбор индексов и источниковHash-checking: детерминизм “на уровне байтов”Типичные ошибки с версиями и lock-файламиОшибка 1: не ставить по lock-файлуОшибка 2: игнорировать разницу Python-версийОшибка 3: смешивать зависимости разработки и productionОшибка 4: обновлять только .in, но не перегенерировать lockОшибка 5: регенерировать lock без контроля индексовОшибка 6: “частичная воспроизводимость” (фиксировать только прямые зависимости)Практический workflow: от пустого репозитория до стабильного окруженияШаг 1. Разметить зависимостиШаг 2. Сгенерировать lockШаг 3. Убедиться, что установка детерминированаШаг 4. В CIГде в этом месте обычно “ломается” производствоСборка wheels и platform-specific пакетыEnvironment markersТиповые примеры для конфигураций с extrasКак сделать систему контроля версий “осмысленной”Почему это полезно даже тем, кто “и так умеет”Вывод: контроль окружений как часть инженерной дисциплины

Проблема “оно работает у меня” в Python — почти всегда не про код. Она про окружение: набор установленных пакетов, их версии, особенности зависимостей и даже то, как именно вы их установили (из PyPI, из локального индекса, с какими опциями, какие ограничения были заданы).

Ситуации знакомы:

  • Локально всё проходит тесты, а в CI падает.
  • На одной машине проект стартует, на другой — вылезает ImportError или конфликт версий.
  • Спустя пару недель после “обновления” библиотеки поведение меняется, но в Git вы не видите никаких изменений.

Чтобы это исправить, важно понять, как Python управляет зависимостями: роль venv, поведение pip, что делает pip-tools, и как устроены lock-файлы. Ниже — практический разбор с конкретными командами, типичными ошибками и правилами, которые реально помогают добиться воспроизводимости.


Почему «работает у меня» — это почти всегда про окружение

В Python есть два уровня “контекста”:

  1. Интерпретатор (версия Python, сборка, платформа).
  2. Набор зависимостей (версии пакетов и их транзитивные зависимости).

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

  • “Установим requirements.txt” — но там либо плавающие версии (requests>=2.20), либо список “что-то вроде” без фиксации.
  • “У меня стоит последняя версия пакета” — а у другого человека она может быть другой, потому что pip install по умолчанию разрешает зависимости в текущий момент времени.
  • “Мы обновляли зависимости” — но обновляли без фиксации, или фиксировали частично.

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


Уровень 1: venv и что он реально гарантирует

venv создаёт изолированную среду Python для проекта: отдельные библиотеки, отдельное дерево site-packages, отдельный pip. Это хорошо — но только частично решает проблему.

Что venv гарантирует

  • Изоляцию пакетов от системной Python-установки.
  • Контроль над тем, какой именно Python-интерпретатор использует проект.

Чего venv не гарантирует

  • Он не фиксирует версии пакетов. Вы создаёте пустое окружение, а затем ставите зависимости — и вот тут всё зависит от того, чем вы их описали и как установили.
  • Он не защитит от того, что кто-то поставит другую версию пакета, потому что pip разрешит зависимости иначе.

Минимальный рабочий сценарий

code
python3 -m venv .venv
source .venv/bin/activate  # или .venv\Scripts\activate в Windows
python -m pip install --upgrade pip

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


Уровень 2: pip, requirements и главная ловушка

pip работает по запросам, а не по “снимку состояния”. Если вы укажете:

  • requests>=2.31 — то pip может поставить любую версию, удовлетворяющую условию, и выбор будет зависеть от момента установки.
  • “просто установим по списку пакетов” — и получится набор версий, который может отличаться между разработчиками.

Если в requirements.txt лежит только список без фиксации (или с широкими ограничениями), вы не получаете детерминизма.

pip и “разрешение зависимостей”

У pip есть алгоритм разрешения зависимостей: он учитывает Requires-Python, маркеры платформы, и требования транзитивных зависимостей. Но итоговый набор версий выбирается на основе того, какие версии доступны в момент установки.

Отсюда — типичная причина “оно работает у меня”: вы установили окружение в один день, а другой разработчик — в другой, и набор версий слегка сместился.


Воспроизводимость: что нужно зафиксировать

Чтобы окружение было воспроизводимым, нужно фиксировать минимум:

  • версии всех прямых зависимостей,
  • версии всех транзитивных зависимостей (то есть фактический граф пакетов),
  • желательно — ограничения по платформе и Python-версии.

Фиксация прямых зависимостей недостаточна: транзитивные версии всё равно могут “уехать”.

Именно поэтому используют lock-файлы.


pip-tools: зачем он нужен

pip-tools — это инструмент, который помогает отделить:

  • описание интентов (какие пакеты нужны и какими ограничениями),
  • от генерации конкретного набора версий (lock).

Он оперирует двумя файлами:

  • requirements.in — “что хотим” (с диапазонами версий, без фиксации всех транзитивных).
  • requirements.txt — “что реально установится” (полный список с точными версиями транзитивных зависимостей).

Ключевая идея

Вы фиксируете результаты разрешения зависимостей один раз — и дальше всем нужно ставить по lock-файлу, а не заново решать граф в каждый момент времени.


Настройка: требования, файлы .in и .txt

Предположим, у проекта есть зависимости:

  • веб-фреймворк,
  • клиент для внешнего API,
  • утилиты для тестов.

1) Создаём requirements.in

code
fastapi>=0.110,<1.0
httpx>=0.25
pydantic>=2.5

Это намерение: не “поставь ровно такие версии”, а “в рамках допустимого диапазона”.

В отдельности обычно делают requirements-dev.in:

code
pytest>=7
ruff>=0.3
mypy>=1.8

2) Генерируем lock: requirements.txt

Теперь используем pip-compile:

code
pip install pip-tools
pip-compile requirements.in --output-file requirements.txt

Результат — файл с точными версиями:

code
fastapi==0.111.0
httpx==0.27.0
pydantic==2.7.3
...

Пара важных нюансов:

  • pip-compile вычисляет граф зависимостей и фиксирует все версии.
  • Это зависит от текущих доступных версий на индексах. Но после генерации lock-файл становится источником правды.

Установка из lock-файла: стабильность вместо догадок

После того как requirements.txt зафиксирован, установка должна быть простой и одинаковой для всех:

code
pip install -r requirements.txt

На разных машинах, при одинаковом Python-интерпретаторе и одинаковых индексах, вы получите одинаковые версии пакетов.


Обновление зависимостей без потери контроля

Когда приходит время обновлять версии, вы не должны “просто переустановить”. Правильный цикл:

  1. Обновить ограничения в .in (например, расширить диапазон или поднять верхнюю границу).
  2. Снова сгенерировать lock: pip-compile.
  3. Протестировать.
  4. Закоммитить изменённые requirements.txt (lock) и .in (намерения).

Пример обновления

code
pip-compile requirements.in --output-file requirements.txt

После этого diff в Git покажет, какие версии действительно изменились — и вы сможете это обсуждать осознанно.


Контроль воспроизводимости по Python-версии

Есть тонкость: доступные версии пакетов зависят от python_version и маркеров. Поэтому желательно явно зафиксировать диапазон Python, а затем проверять, что lock строится для ожидаемой версии.

Практический подход

  • В документации проекта укажите, какую версию Python поддерживаете.
  • Сгенерируйте lock в среде нужной версии.
  • CI должен использовать тот же Python, что использовался при генерации lock (или близко совместимую).

Если вы сгенерировали lock на Python 3.11, а потом пытаетесь установить на Python 3.9, возможно получение конфликтов или другой набор пакетов.


Разделение окружений: prod, dev, тесты

В реальном проекте вы почти всегда используете несколько наборов зависимостей:

  • для production,
  • для разработки (линтеры, форматтеры, тестовые инструменты),
  • возможно — для разных этапов (например, миграции, сборка доков).

Обычно делают два lock-файла:

  • requirements.txt (prod),
  • requirements-dev.txt (dev).

Пример генерации dev lock

code
pip-compile requirements-dev.in --output-file requirements-dev.txt

Установка

code
pip install -r requirements.txt
pip install -r requirements-dev.txt

Это повышает прозрачность: разработчик понимает, что именно ставится “для работы” и что — “для удобства”.


Важные опции pip-compile: что влияет на повторяемость

У pip-compile есть параметры, которые часто нужны на практике.

Выбор индексов и источников

Если вы используете private index или зеркала PyPI, установки должны быть одинаковыми для генерации lock и последующей установки. Иначе lock может ссылаться на другие версии, чем вы получите при установке.

Hash-checking: детерминизм “на уровне байтов”

Для максимальной строгости можно включить генерацию хэшей:

code
pip-compile --generate-hashes requirements.in

Тогда в lock-файле будут указаны --hash=... для каждого пакета. При установке pip будет проверять, что скачанные артефакты совпадают с теми, что были зафиксированы.

Это особенно полезно в корпоративных средах и при работе с регламентами по supply-chain security. Но нужно учитывать, что хэши меняются, если пакет переупакован или доступен в другом виде (например, из-за different build).


Типичные ошибки с версиями и lock-файлами

Ниже — то, что чаще всего ломает воспроизводимость даже при наличии pip-tools.

Ошибка 1: не ставить по lock-файлу

Случайный человек может сделать:

code
pip install -r requirements.in

requirements.in для pip — это тоже “input”, но там могут быть диапазоны. Итог — снова разрешение зависимостей “на лету”.

Правило: ставим по .txt (lock), .in используем только для генерации.


Ошибка 2: игнорировать разницу Python-версий

Если в CI стоит Python 3.10, а локально 3.12, то версии некоторых пакетов могут различаться из-за Requires-Python.

Правило: генерируйте lock на той версии Python, которая используется в целевой среде (или поддерживайте несколько lock для разных мажорных версий, если проект так устроен).


Ошибка 3: смешивать зависимости разработки и production

Когда кто-то включает dev-зависимости в прод lock, вы:

  • увеличиваете attack surface,
  • усложняете обновления,
  • получаете расхождения поведения между окружениями.

Правило: отдельные файлы для prod/dev, отдельные lock.


Ошибка 4: обновлять только .in, но не перегенерировать lock

Вы меняете requirements.in, поднимаете ограничения, но requirements.txt в Git не обновляете. В результате проект использует старый граф зависимостей.

Правило: изменения в .in всегда сопровождаются регенерацией lock и коммитом результата.


Ошибка 5: регенерировать lock без контроля индексов

Если вчера использовался один индекс (например, corporate mirror), а сегодня — прямой pypi.org, итог может отличаться.

Правило: одинаковая конфигурация индексов при генерации и установке. Часто это настраивается через конфиг pip и CI.


Ошибка 6: “частичная воспроизводимость” (фиксировать только прямые зависимости)

Люди иногда делают так:

  • фиксируют requests==...,
  • но оставляют transitive зависимости в диапазонах через >=.

В итоге транзитивный набор может меняться.

Правило: фиксировать именно то, что установится — то есть транзитивы тоже. Lock-файл это делает.


Практический workflow: от пустого репозитория до стабильного окружения

Вот рабочий процесс, который можно адаптировать под любую команду.

Шаг 1. Разметить зависимости

  • requirements.in — prod
  • requirements-dev.in — dev

Шаг 2. Сгенерировать lock

code
pip install pip-tools
pip-compile requirements.in --output-file requirements.txt
pip-compile requirements-dev.in --output-file requirements-dev.txt

Шаг 3. Убедиться, что установка детерминирована

В новом окружении (например, на чистой машине) выполнить:

code
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
pip install -r requirements-dev.txt

Проверка: версии должны совпасть с теми, что в lock.

Шаг 4. В CI

  • фиксировать Python-версию,
  • устанавливать по lock,
  • запускать тесты.

Где в этом месте обычно “ломается” производство

Есть несколько неочевидных факторов, которые тоже влияют на поведение.

Сборка wheels и platform-specific пакеты

Некоторые пакеты имеют зависимости на системные библиотеки или используют wheels, зависящие от платформы. На Linux и macOS может быть разный набор wheel, даже при одинаковом lock (хотя версии самих пакетов будут те же).

Если у вас есть такие зависимости — важно тестировать на целевых платформах.

Environment markers

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


Типовые примеры для конфигураций с extras

Если зависимости включают extras, намерение выглядит так:

requirements.in:

code
fastapi[standard]>=0.110

pip-compile корректно развернёт extras в транзитивный набор и зафиксирует реальные зависимости. Это ещё один аргумент в пользу генерации lock: вы хотите видеть результат, а не гадать.


Как сделать систему контроля версий “осмысленной”

Чтобы команда не утонула в диффах, стоит договориться:

  • lock-файлы коммитятся всегда (иначе воспроизводимость теряется),
  • обновления зависимостей делаются отдельными коммитами или PR,
  • изменения в .in без соответствующих изменений в lock — не принимаются,
  • желательно добавить в проект короткую документацию “как обновлять зависимости”.

Минимально это может выглядеть так:

  • requirements.in — намерения
  • requirements.txt — lock
  • обновление: правим .in → запускаем pip-compile → коммитим .txt

Почему это полезно даже тем, кто “и так умеет”

Те, кто работает в одиночку, иногда считают, что им достаточно “следить глазами за версиями” или периодически запускать pip install -U. Это помогает до момента, когда:

  • появляется второй разработчик,
  • приходит CI,
  • вы запускаете проект в Docker,
  • вы обновляете пакет, и “вроде всё так же”, но тесты начали падать.

Lock-файл — это не бюрократия. Это механизм фиксации принятого решения по зависимостям, который:

  • уменьшает вариативность,
  • делает PR по обновлениям предсказуемыми,
  • помогает расследовать регрессии.

Вывод: контроль окружений как часть инженерной дисциплины

venv решает проблему изоляции, но не гарантирует повторяемости. pip может переоценить зависимости в момент установки, если вы оставляете диапазоны. Поэтому практический путь к воспроизводимости выглядит так:

  1. Создаём изолированное окружение (venv).
  2. Описываем намерения в .in.
  3. Генерируем lock-файл через pip-compile.
  4. Ставим зависимости только из lock (requirements.txt).
  5. Обновляем зависимости контролируемо: правим .in → пересчитываем lock → коммитим.
  6. Фиксируем Python-версию в CI и желательно — на этапе генерации lock.

Если вам нужно углубиться именно в механизм работы pip-tools (включая опции, хэши и подходы к multi-platform), полезно пройти структурированный материал, например курс по теме управления зависимостями и воспроизводимыми окружениями: изучить на практике.

Главное — не “собирать окружение каждый раз заново”, а фиксировать результат. Тогда “оно работает у меня” превращается в инженерно доказуемое “оно будет работать так же”.

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

Автор

$ sudo teach IT

Продолжите обучение

Все курсы
Python – для начинающих!

Python – для начинающих!

С нуля до профессионального уровня. Подходит для всех. Учитесь каждый день и овладейте самым популярным языком программирования.

Перейти к курсу
Простыми словами о защищенном соединении

Простыми словами о защищенном соединении

Базовый курс для обычных людей: что такое защищённое соединение, как оно устроено, что оно умеет и чего не умеет, как выбрать сервис и настроить его на своих устройствах.

Перейти к курсу
Ботостроение Telegram

Ботостроение Telegram

Лёгкий, быстрый и доступный способ познакомиться с миром ботостроения в Telegram. Видео, конспекты, практика и помощь – всё у нас на курсе.

Перейти к курсу
Сервис, который не падает - как построить безопасную зашифрованную сеть

Сервис, который не падает - как построить безопасную зашифрованную сеть

Практический курс по безопасности сети: как собрать собственный сервис защищённого доступа - от пустого сервера до подписки с автовыбором - и, что важнее, как потом не уронить его своими руками.

Перейти к курсу

Другие статьи

Python для аналитики на практике: как строить признаки и проверять их воспроизводимость
python

Python для аналитики на практике: как строить признаки и проверять их воспроизводимость

Поймём, где чаще всего ломается воспроизводимость (версии данных, фильтры, дрейф) и как оформить пайплайн признаков так, чтобы результаты совпадали между запусками и окружениями.

17 сентября 2026 г.
1040
Системный дебаг памяти и производительности в Python: где искать утечки и как проверять гипотезы
системный

Системный дебаг памяти и производительности в Python: где искать утечки и как проверять гипотезы

Пошагово разберём типовые причины роста памяти (кэширование, циклические ссылки, удержание объектов в замыканиях, неверная работа с ресурсами) и как подтверждать догадки трейсами, профилировщиками и метриками. В конце — чек-лист расследования для задач из

15 сентября 2026 г.
1030
Создание Telegram бота в 2026 легко и просто! Полный курсы!
создание

Создание Telegram бота в 2026 легко и просто! Полный курсы!

19 июня 2026 г.
1431
Как выбрать, куда идти в IT: веб, боты, данные — без хаоса
выбрать

Как выбрать, куда идти в IT: веб, боты, данные — без хаоса

Разберём самые понятные направления для новичка (веб, боты, аналитика/данные) и чем они отличаются по шагам обучения. В конце вы получите простой план, как проверить, что вам подходит, и какой стартовый путь выбрать.

5 октября 2026 г.
580
Путь от README до демо: как собрать портфолио разработчика, чтобы его хотели смотреть
путь

Путь от README до демо: как собрать портфолио разработчика, чтобы его хотели смотреть

Разберём структуру репозитория, как упаковать проект в понятную историю (проблема → решение → запуск → метрики качества) и какие артефакты повышают доверие: скринкасты, архитектурная схема, чек-лист продакшн-готовности.

17 сентября 2026 г.
1070
Как понять, какой язык программирования учить первым: простой тест и выбор
понять

Как понять, какой язык программирования учить первым: простой тест и выбор

Помогу новичку выбрать первый язык без стресса: объясню варианты по целям (веб, боты, анализ данных), что учить в первую очередь и как не потратить месяцы впустую. В конце — краткий план старта на ближайшие недели.

2 октября 2026 г.
630

Комментарии

Станьте первым в обсуждении

Поделитесь мнением о статье или задайте вопрос автору

Содержание

Почему «работает у меня» — это почти всегда про окружениеУровень 1: venv и что он реально гарантируетЧто venv гарантируетЧего venv не гарантируетМинимальный рабочий сценарийУровень 2: pip, requirements и главная ловушкаpip и “разрешение зависимостей”Воспроизводимость: что нужно зафиксироватьpip-tools: зачем он нуженКлючевая идеяНастройка: требования, файлы .in и .txt1) Создаём requirements.in2) Генерируем lock: requirements.txtУстановка из lock-файла: стабильность вместо догадокОбновление зависимостей без потери контроляПример обновленияКонтроль воспроизводимости по Python-версииПрактический подходРазделение окружений: prod, dev, тестыПример генерации dev lockУстановкаВажные опции pip-compile: что влияет на повторяемостьВыбор индексов и источниковHash-checking: детерминизм “на уровне байтов”Типичные ошибки с версиями и lock-файламиОшибка 1: не ставить по lock-файлуОшибка 2: игнорировать разницу Python-версийОшибка 3: смешивать зависимости разработки и productionОшибка 4: обновлять только .in, но не перегенерировать lockОшибка 5: регенерировать lock без контроля индексовОшибка 6: “частичная воспроизводимость” (фиксировать только прямые зависимости)Практический workflow: от пустого репозитория до стабильного окруженияШаг 1. Разметить зависимостиШаг 2. Сгенерировать lockШаг 3. Убедиться, что установка детерминированаШаг 4. В CIГде в этом месте обычно “ломается” производствоСборка wheels и platform-specific пакетыEnvironment markersТиповые примеры для конфигураций с extrasКак сделать систему контроля версий “осмысленной”Почему это полезно даже тем, кто “и так умеет”Вывод: контроль окружений как часть инженерной дисциплины