Упаковка и зависимости Python
Поставил в чистый образ одну популярную библиотеку без указания версии. Приехало пять пакетов: сама библиотека версии 2.34.2 и четыре её зависимости - 2026.7.22, 3.4.9, 3.18 и 2.7.0. Общее число пакетов в образе стало 12. Ни одну из четырёх я не выбирал и ни одну версию не называл.
Стержень: воспроизводимость держится на закреплении ВСЕХ версий, включая те, которые вы не ставили руками.
// Формулировки: «зачем виртуальное окружение?», «чем pinning отличается от lock-файла?», «как обновлять зависимости?»
Отдельное окружение
Виртуальное окружение - это отдельный каталог со своим набором пакетов. Без него все проекты на машине делят один набор, и два проекта с разными требованиями к одной библиотеке уживаются только случайно.
В контейнерах вопрос стоит иначе: там уже есть изоляция, и отдельное окружение внутри образа многие пропускают. Это допустимо, но с оговоркой - системные пакеты дистрибутива и пакеты приложения начинают жить вместе, и обновление системы может задеть приложение. Поэтому в образах либо используют окружение, либо явно ставят приложение отдельно от системного интерпретатора.
// Главное, чего добиваются в обоих случаях: набор пакетов должен быть описан файлом и восстанавливаться из него одной командой. Окружение, собранное руками и не описанное, - это ровно та машина-снежинка, которую невозможно воспроизвести.
- виртуальное окружение
- отдельный набор пакетов под конкретный проект
- восстановимость
- набор пакетов собирается из файла одной командой
Закрепление и файл замков
Есть два разных списка, и путать их дорого. Первый - список ТРЕБОВАНИЙ: что нужно приложению и в каких границах версий. Второй - список того, что реально встало, со всеми транзитивными зависимостями и точными версиями. Мой замер показывает, зачем нужен второй: из одной названной библиотеки выросло пять пакетов.
Файл замков хранит именно второй список, часто ещё и с контрольными суммами пакетов. Тогда сборка сегодня и через полгода даёт одно и то же, а подмена пакета в хранилище обнаруживается сразу.
// Практика такая: требования пишем с разумными границами, файл замков коммитим в репозиторий, а образы собираем строго по нему. Обновление делаем отдельным изменением: подняли версии, прогнали тесты, посмотрели глазами. Как только эти две вещи смешиваются - «обновим заодно, пока правим фичу» - находить причину поломки становится вдвое дороже.
- список требований
- что нужно приложению и в каких границах версий
- файл замков
- точные версии всего, что встало, включая транзитивные зависимости
Обновление и конфликты
Закрепили и забыли - вторая типовая беда. Через год версии разъехались на несколько крупных выпусков, и обновление ломает всё разом. Дешевле обновляться понемногу и часто: робот раз в неделю поднимает версии и открывает изменение, тесты проходят, человек смотрит и вливает.
Конфликты версий возникают, когда две библиотеки требуют разные несовместимые версии третьей. Разрешаются они одним из трёх способов: обновить ту, что отстала; заменить одну из библиотек; в крайнем случае - вынести конфликтующую часть в отдельный сервис. Волшебной кнопки тут нет, и чем свежее ваши версии, тем реже это случается.
// Ещё одна вещь, о которой спрашивают: где брать пакеты. Публичное хранилище может быть недоступно или отдать не тот пакет, поэтому в компаниях ставят своё зеркало и собирают только через него. Заодно это даёт возможность запретить пакеты с известными уязвимостями централизованно.
- регулярное обновление
- робот поднимает версии, тесты проверяют, человек вливает
- своё зеркало
- внутреннее хранилище пакетов; независимость и контроль
Как отвечать: «Чем pinning отличается от lock-файла и что нужно проекту?»
Закрепление - это указание точных версий тех библиотек, которые вы назвали сами. Файл замков - это точные версии ВСЕГО, что реально встало, включая транзитивные зависимости, часто с контрольными суммами. Разница видна на замере: я поставил одну библиотеку без версии, и приехало пять пакетов - сама она и четыре чужие, ни одну из которых я не выбирал. Если закрепить только свою, обновление любой из этих четырёх приедет незаметно и однажды сломает сборку. Поэтому проекту нужно и то и другое: требования с разумными границами для людей и файл замков для воспроизводимости, причём файл замков коммитится в репозиторий, а образы собираются строго по нему. И обязательно регулярное обновление замков отдельным изменением с прогоном тестов, иначе через год обновиться будет невозможно.
Ответ различает два списка и показывает разницу измеренным примером. Требование обновлять регулярно закрывает вторую половину проблемы, о которой обычно молчат.
На чём валятся
- −Закрепляют только свои библиотеки и получают неожиданные обновления транзитивных.
- −Не коммитят файл замков, и у каждого своя сборка.
- −Закрепляют версии и не обновляют их годами.
- −Смешивают обновление зависимостей с правкой функциональности в одном изменении.
- −Собирают прод-образы напрямую из публичного хранилища без своего зеркала.
Проверьте себя
Пять вопросов из банка по этой подтеме. Всего их 14, остальные разбираются в тренажёре.
- Зачем
pip install -e .(editable-режим) при разработке пакета?A)Устанавливает пакет с шифрованием исходного кода для защитыB)Ставит пакет ссылкой на исходники — правки видны без переустановкиC)Ставит только указанные extra-зависимости, пропуская основныеD)Компилирует пакет в бинарник для ускорения его импорта в продепоказать ответ и разбор
+B)Ставит пакет ссылкой на исходники — правки видны без переустановки// разбор: editable-установка (-e) регистрирует пакет как ссылку на его исходники в дереве проекта, а не копирует их в site-packages. Правишь код — изменения сразу подхватываются при импорте, без pip install заново. Удобно при разработке библиотеки или монорепо; для прод-деплоя ставят обычным способом.
- Чем lock-файл (poetry.lock, pip-tools) полезнее обычного requirements с диапазонами?A)Фиксирует точные версии всего дерева, включая транзитивныеB)Он нужен только чтобы pip показывал дерево зависимостей человекуC)Он автоматически обновляет зависимости до последних версий при сборкеD)Он позволяет ставить пакеты вообще без доступа к сети из кэша
показать ответ и разбор
+A)Фиксирует точные версии всего дерева, включая транзитивные// разбор: Обычный requirements с диапазонами фиксирует прямые зависимости, но транзитивные (зависимости зависимостей) всё равно плавают. Lock-файл записывает точные версии всего графа с хешами — сборка на любой машине и в любой момент даёт идентичный набор. Обновляют его осознанно отдельной командой.
- Зачем проекту виртуальное окружение (venv)?A)Позволить запускать проект без установленного в системе интерпретатора Python вообщеB)Зашифровать установленные пакеты, чтобы к ним не было доступа у других пользователейC)Изолировать зависимости проекта от системных и от других проектовD)Ускорить установку пакетов за счёт скачивания их из локального кеша окружения
показать ответ и разбор
+C)Изолировать зависимости проекта от системных и от других проектов// разбор: Виртуальное окружение — отдельный каталог со своим набором установленных пакетов (и ссылкой на интерпретатор). Оно изолирует зависимости проекта: разные проекты держат разные, порой несовместимые версии одних библиотек, не конфликтуя между собой и не засоряя системный Python. Это база воспроизводимости: окружение создают из зафиксированного списка зависимостей.
- Чем 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 под платформу нет.
- Зачем нужен lock-файл (poetry.lock, pip-tools) сверх списка зависимостей?A)Запрещает добавлять в проект новые зависимости после первой фиксации набора пакетовB)Ускоряет установку, заранее скачивая все пакеты проекта в постоянный локальный кешC)Хранит исходный код всех зависимостей внутри репозитория проекта для автономностиD)Фиксирует точные версии всех зависимостей (и транзитивных) для воспроизводимой установки
показать ответ и разбор
+D)Фиксирует точные версии всех зависимостей (и транзитивных) для воспроизводимой установки// разбор: Список зависимостей (pyproject/requirements с диапазонами) задаёт, что нужно; lock-файл фиксирует, ЧТО именно установилось — точные версии всех пакетов, включая транзитивные, часто с хешами. Благодаря ему установка воспроизводима: на любой машине и в CI ставится идентичный набор, а не «свежайшее по диапазону», что спасает от «у меня работало». Lock коммитят в репозиторий.
дальше
Теорию прочитали. Навык ставится повторением
В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.