Прямой ответ
Python-пакет распространяется в двух форматах. sdist (.tar.gz) — архив исходников, требующий сборки при установке. Wheel (.whl) — собранный дистрибутив, который распаковывается в место установки без шага сборки.
| sdist | wheel | |
|---|---|---|
| Расширение | .tar.gz |
.whl |
| Содержимое | Исходники и метаданные | Готовые к установке файлы |
| Сборка при установке | Требуется | Не требуется |
| Скорость установки | Ниже | Выше |
| Зависит от платформы | Нет | Может зависеть |
| Требует компилятора | Да, при наличии расширений | Нет |
Имя wheel-файла кодирует совместимость:
numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.whl
│ │ │ │ └── платформа и ABI Linux
│ │ │ └── ABI Python
│ │ └── версия Python (CPython 3.12)
│ └── версия пакета
└── имя пакета
Для пакета без бинарных расширений теги универсальны:
my_package-1.2.0-py3-none-any.whl
Они означают: любой Python 3, без привязки к ABI, любая платформа. Такой wheel устанавливается везде без компиляции.
Практическое следствие: при отсутствии подходящего wheel установка идёт из sdist, что требует компилятора и заголовочных файлов системных библиотек. В контейнере на минимальном базовом образе это заканчивается ошибкой сборки; о сборке образов — в статье про multi-stage образы.
Платформенные wheel решают проблему для распространённых сочетаний. Теги manylinux описывают совместимость с широким набором дистрибутивов Linux: пакет, собранный под manylinux_2_17, работает в системах с glibc 2.17 и новее.
Публиковать стоит оба формата: sdist как источник истины и возможность сборки под платформу без готового wheel, wheel — для быстрой установки; о процессе — в статье про публикацию в PyPI.
Структура дистрибутивов и что в них находится
Форматы различаются не только наличием шага сборки, но и содержимым.
Содержимое sdist. Архив включает исходники, метаданные, файлы сборки и — при корректной настройке — тесты и документацию. Получатель может собрать пакет самостоятельно, поэтому в архиве должно быть всё необходимое для сборки.
Содержимое wheel. Архив содержит готовые к установке файлы и каталог метаданных:
my_package/
__init__.py
core.py
my_package-1.2.0.dist-info/
METADATA # имя, версия, зависимости, описание
WHEEL # формат и теги совместимости
RECORD # перечень файлов с контрольными суммами
entry_points.txt
Каталог метаданных создаётся при сборке, поэтому зависимости и версия уже подготовлены: установка не выполняет код сборки и не требует интерпретации конфигурации проекта.
Файл RECORD содержит контрольные суммы установленных файлов, что позволяет проверить целостность установки и корректно удалить пакет.
Практические следствия различий. Из sdist нельзя узнать точные зависимости без запуска сборки, поскольку они могут вычисляться динамически. Из wheel метаданные читаются напрямую, что ускоряет разрешение зависимостей при установке большого набора пакетов.
Отдельное следствие касается проверяемости: содержимое wheel можно перечислить и сравнить с ожидаемым без установки, тогда как проверка sdist требует распаковки архива и анализа конфигурации сборки.
Третье следствие — безопасность. Метаданные wheel зафиксированы при сборке и не могут измениться при установке, тогда как сборка из sdist выполняет код конфигурации на машине пользователя. Этот код обычно безвреден, но он выполняется с правами пользователя и имеет доступ к сети, поэтому установка из исходников требует большего доверия к источнику, чем установка готового дистрибутива.
Практика: работа с форматами дистрибутивов
1. Публикуйте и sdist, и wheel. Исходный дистрибутив позволяет собрать пакет там, где готового wheel нет, а wheel ускоряет установку в остальных случаях.
2. Используйте универсальный тег для чистого Python. Пакет без расширений собирается в py3-none-any, который подходит для всех платформ.
3. Проверяйте содержимое wheel. Архив распаковывается, и перечень файлов показывает, попали ли данные и подпакеты в дистрибутив; о настройке — в статье про структуру пакета.
4. Собирайте платформенные wheel в конвейере. Сборка на машине разработчика даёт wheel под его платформу, который не установится в других; о бинарных расширениях — в статье про сборку расширений.
5. Не собирайте wheel вручную для каждой платформы. Инструменты сборки создают wheel для набора платформ в контейнерах, что обеспечивает совместимость.
6. Проверяйте установку из обоих форматов. Установка из sdist выявляет ошибки сборки, которые wheel скрывает.
7. Учитывайте размер дистрибутива. Wheel с бинарными расширениями под несколько платформ увеличивает объём загрузки в несколько раз.
8. Фиксируйте версию инструмента сборки. Разные версии могут давать различающееся содержимое, что затрудняет воспроизведение.
Рекомендации:
- Проверяйте теги wheel перед публикацией. Неверный тег делает пакет неустанавливаемым там, где он должен работать.
- Не удаляйте старые версии дистрибутивов. Пользователи с фиксацией версии теряют возможность установки.
- Тестируйте установку в минимальном образе. Отсутствие компилятора в контейнере выявляет зависимость от сборки из sdist.
- Различайте имя пакета и имя модуля. В имени дистрибутива допускается дефис, в имени модуля для импорта — нет.
- Проверяйте метаданные внутри wheel. Ошибка в метаданных приводит к неверному разрешению зависимостей у пользователя.
Практический сценарий: сбой установки в контейнере из-за отсутствия платформенного wheel
Контекст: образ собирался на минимальном базовом дистрибутиве. Установка зависимостей падала с ошибкой компиляции, хотя на машине разработчика работала.
- Диагностика. Одна из зависимостей не имела готового wheel для сочетания версии Python и платформы в контейнере. Установка пошла из sdist, что потребовало компилятор и заголовочные файлы, которых в минимальном образе не было.
- Причина. Образ собирался на облегчённом базовом дистрибутиве без инструментов сборки. На машине разработчика они присутствовали, поэтому ошибка не воспроизводилась локально.
- Действия. (а) Версию зависимости обновили до выпуска с готовым wheel для нужной платформы; (б) сборку перевели на многоэтапный образ: установка зависимостей на этапе со инструментами, копирование результата в минимальный образ; (в) добавили проверку наличия wheel для используемой платформы перед обновлением зависимости; (г) для собственного пакета настроили сборку платформенных wheel в конвейере; (д) добавили шаг установки из sdist в тесты, чтобы выявлять такие случаи заранее.
- Результат. Сборка образа перестала требовать компилятора, а размер итогового образа снизился благодаря многоэтапной сборке.
Вывод: wheel устраняет зависимость от инструментов сборки на стороне пользователя, но только при наличии дистрибутива под нужную платформу. Отсутствие подходящего wheel переводит установку в сборку из исходников, что требует компилятора и ломает установку в минимальных образах.
FAQ
Что устанавливается, если есть оба формата? Wheel, если его теги соответствуют версии Python и платформе. При отсутствии подходящего wheel установка идёт из sdist.
Нужен ли sdist, если есть wheel? Да. Исходный дистрибутив позволяет собрать пакет под платформу без готового wheel и служит источником для проверки.
Что означает тег manylinux? Совместимость с дистрибутивами Linux, использующими glibc указанной версии и новее. Это позволяет одному wheel работать в разных системах.
Почему wheel устанавливается быстрее? Он не требует шага сборки: файлы распаковываются в место установки, а метаданные уже подготовлены.
Источники: Python Packaging User Guide, PEP 427 (wheel), PEP 600 (manylinux) — ссылки в шапке.