сеньорчикОткрыть в Telegram
← вся теориятеория к собесу · Рантайм Python

Упаковка и зависимости Python

Зависимости и окружение

Поставил в чистый образ одну популярную библиотеку без указания версии. Приехало пять пакетов: сама библиотека версии 2.34.2 и четыре её зависимости - 2026.7.22, 3.4.9, 3.18 и 2.7.0. Общее число пакетов в образе стало 12. Ни одну из четырёх я не выбирал и ни одну версию не называл.

Стержень: воспроизводимость держится на закреплении ВСЕХ версий, включая те, которые вы не ставили руками.

// Формулировки: «зачем виртуальное окружение?», «чем pinning отличается от lock-файла?», «как обновлять зависимости?»

Отдельное окружение

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

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

// Главное, чего добиваются в обоих случаях: набор пакетов должен быть описан файлом и восстанавливаться из него одной командой. Окружение, собранное руками и не описанное, - это ровно та машина-снежинка, которую невозможно воспроизвести.

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

Закрепление и файл замков

Есть два разных списка, и путать их дорого. Первый - список ТРЕБОВАНИЙ: что нужно приложению и в каких границах версий. Второй - список того, что реально встало, со всеми транзитивными зависимостями и точными версиями. Мой замер показывает, зачем нужен второй: из одной названной библиотеки выросло пять пакетов.

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

// Практика такая: требования пишем с разумными границами, файл замков коммитим в репозиторий, а образы собираем строго по нему. Обновление делаем отдельным изменением: подняли версии, прогнали тесты, посмотрели глазами. Как только эти две вещи смешиваются - «обновим заодно, пока правим фичу» - находить причину поломки становится вдвое дороже.

список требований
что нужно приложению и в каких границах версий
файл замков
точные версии всего, что встало, включая транзитивные зависимости

Обновление и конфликты

Закрепили и забыли - вторая типовая беда. Через год версии разъехались на несколько крупных выпусков, и обновление ломает всё разом. Дешевле обновляться понемногу и часто: робот раз в неделю поднимает версии и открывает изменение, тесты проходят, человек смотрит и вливает.

Конфликты версий возникают, когда две библиотеки требуют разные несовместимые версии третьей. Разрешаются они одним из трёх способов: обновить ту, что отстала; заменить одну из библиотек; в крайнем случае - вынести конфликтующую часть в отдельный сервис. Волшебной кнопки тут нет, и чем свежее ваши версии, тем реже это случается.

// Ещё одна вещь, о которой спрашивают: где брать пакеты. Публичное хранилище может быть недоступно или отдать не тот пакет, поэтому в компаниях ставят своё зеркало и собирают только через него. Заодно это даёт возможность запретить пакеты с известными уязвимостями централизованно.

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

Как отвечать: «Чем pinning отличается от lock-файла и что нужно проекту?»

Закрепление - это указание точных версий тех библиотек, которые вы назвали сами. Файл замков - это точные версии ВСЕГО, что реально встало, включая транзитивные зависимости, часто с контрольными суммами. Разница видна на замере: я поставил одну библиотеку без версии, и приехало пять пакетов - сама она и четыре чужие, ни одну из которых я не выбирал. Если закрепить только свою, обновление любой из этих четырёх приедет незаметно и однажды сломает сборку. Поэтому проекту нужно и то и другое: требования с разумными границами для людей и файл замков для воспроизводимости, причём файл замков коммитится в репозиторий, а образы собираются строго по нему. И обязательно регулярное обновление замков отдельным изменением с прогоном тестов, иначе через год обновиться будет невозможно.

Ответ различает два списка и показывает разницу измеренным примером. Требование обновлять регулярно закрывает вторую половину проблемы, о которой обычно молчат.

На чём валятся

  • Закрепляют только свои библиотеки и получают неожиданные обновления транзитивных.
  • Не коммитят файл замков, и у каждого своя сборка.
  • Закрепляют версии и не обновляют их годами.
  • Смешивают обновление зависимостей с правкой функциональности в одном изменении.
  • Собирают прод-образы напрямую из публичного хранилища без своего зеркала.

Проверьте себя

Пять вопросов из банка по этой подтеме. Всего их 14, остальные разбираются в тренажёре.

  1. #packaging_deps1 / 5
    Зачем pip install -e . (editable-режим) при разработке пакета?
    A)Устанавливает пакет с шифрованием исходного кода для защиты
    B)Ставит пакет ссылкой на исходники — правки видны без переустановки
    C)Ставит только указанные extra-зависимости, пропуская основные
    D)Компилирует пакет в бинарник для ускорения его импорта в проде
    показать ответ и разбор
    +B)Ставит пакет ссылкой на исходники — правки видны без переустановки

    // разбор: editable-установка (-e) регистрирует пакет как ссылку на его исходники в дереве проекта, а не копирует их в site-packages. Правишь код — изменения сразу подхватываются при импорте, без pip install заново. Удобно при разработке библиотеки или монорепо; для прод-деплоя ставят обычным способом.

  2. #packaging_deps2 / 5
    Чем lock-файл (poetry.lock, pip-tools) полезнее обычного requirements с диапазонами?
    A)Фиксирует точные версии всего дерева, включая транзитивные
    B)Он нужен только чтобы pip показывал дерево зависимостей человеку
    C)Он автоматически обновляет зависимости до последних версий при сборке
    D)Он позволяет ставить пакеты вообще без доступа к сети из кэша
    показать ответ и разбор
    +A)Фиксирует точные версии всего дерева, включая транзитивные

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

  3. #packaging_deps3 / 5
    Зачем проекту виртуальное окружение (venv)?
    A)Позволить запускать проект без установленного в системе интерпретатора Python вообще
    B)Зашифровать установленные пакеты, чтобы к ним не было доступа у других пользователей
    C)Изолировать зависимости проекта от системных и от других проектов
    D)Ускорить установку пакетов за счёт скачивания их из локального кеша окружения
    показать ответ и разбор
    +C)Изолировать зависимости проекта от системных и от других проектов

    // разбор: Виртуальное окружение — отдельный каталог со своим набором установленных пакетов (и ссылкой на интерпретатор). Оно изолирует зависимости проекта: разные проекты держат разные, порой несовместимые версии одних библиотек, не конфликтуя между собой и не засоряя системный Python. Это база воспроизводимости: окружение создают из зафиксированного списка зависимостей.

  4. #packaging_deps4 / 5
    Чем sdist отличается от wheel как формата пакета?
    A)sdist предназначен для приложений, а wheel — исключительно для библиотек с C-кодом
    B)wheel содержит исходники и требует сборки, а sdist — уже готовый к установке бинарник
    C)sdist — исходники (нужна сборка), wheel — готовый к установке бинарный формат
    D)Это два названия одного формата: разница только в расширении файла архива пакета
    показать ответ и разбор
    +C)sdist — исходники (нужна сборка), wheel — готовый к установке бинарный формат

    // разбор: sdist (source distribution) — архив с исходниками и метаданными; при установке его нужно собрать (иногда с компиляцией C-расширений). wheel — предсобранный бинарный формат: pip ставит его быстро, распаковкой, без шага сборки. Поэтому wheel предпочтителен (быстрее, не требует компиляторов у пользователя); sdist остаётся как исходный вариант и fallback, если готового wheel под платформу нет.

  5. #packaging_deps5 / 5
    Зачем нужен lock-файл (poetry.lock, pip-tools) сверх списка зависимостей?
    A)Запрещает добавлять в проект новые зависимости после первой фиксации набора пакетов
    B)Ускоряет установку, заранее скачивая все пакеты проекта в постоянный локальный кеш
    C)Хранит исходный код всех зависимостей внутри репозитория проекта для автономности
    D)Фиксирует точные версии всех зависимостей (и транзитивных) для воспроизводимой установки
    показать ответ и разбор
    +D)Фиксирует точные версии всех зависимостей (и транзитивных) для воспроизводимой установки

    // разбор: Список зависимостей (pyproject/requirements с диапазонами) задаёт, что нужно; lock-файл фиксирует, ЧТО именно установилось — точные версии всех пакетов, включая транзитивные, часто с хешами. Благодаря ему установка воспроизводима: на любой машине и в CI ставится идентичный набор, а не «свежайшее по диапазону», что спасает от «у меня работало». Lock коммитят в репозиторий.

дальше

Теорию прочитали. Навык ставится повторением

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