Модули и упаковка

Что это такое

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

Почему это важно

Почти каждый случай «у меня работает» в Python объясняется окружением, а не кодом: двум проектам нужны разные версии одной библиотеки, системный интерпретатор трогать нельзя, зависимость поставили для теста и не записали. Виртуальные окружения и записанный список зависимостей снимают эту неоднозначность. Однако система импорта важна по другой причине: импорт исполняет файл, поэтому побочный эффект на уровне модуля превращает граф зависимостей в последовательность действий, и потому сбои запуска в Python часто оказываются проблемой импорта.

Ключевые идеи

Виртуальное окружение изолирует зависимости одного проекта

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

project/.venv/bin/python -m pip install -r requirements.txt

Список зависимостей нужно записывать, лучше с версиями

Незаписанная зависимость работает ровно до тех пор, пока проект не начнёт собирать кто-то другой. Запись прямых зависимостей и их версий делает сборку воспроизводимой, а фиксация всего транзитивного набора — точной; и то и другое лучше комментария в readme со списком того, что автор случайно запомнил.

Импорт исполняет модуль

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

Защита main делает файл и модулем, и программой

Код под проверкой if __name__ == "__main__": выполняется только тогда, когда файл запущен напрямую, а не когда его импортируют. Точка входа под этой защитой позволяет брать из файла определения, не запуская программу, — именно поэтому файл поддаётся тестированию.

Пакеты — это каталоги с ясной публичной поверхностью

Импорт из пакета вносит его подмодули в область видимости, и то, что пакет открывает наружу, становится его интерфейсом к остальному проекту. Если держать поверхность небольшой — несколько имён, намеренно реэкспортированных, — внутренние перестройки не расходятся по всем, кто пакет вызывает.

Метаданные упаковки описывают то, что распространяется

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

Два модуля, которые импортируют друг друга, — обычно один замысел, разделённый не там, где нужно, либо общая часть, которой место в третьем модуле. Импорт, отложенный внутрь функции, перестаёт давать сбой, но цикла не убирает, поэтому саму структуру всё ещё стоит пересмотреть.

Практика

Изолируйте зависимости проекта

Создайте виртуальное окружение для небольшого проекта, установите в него зависимости и запишите их с версиями.

Готово, когда: второе окружение, собранное по записанному списку, запускает проект так же.

Сделайте скрипт импортируемым

Возьмите скрипт, логика которого выполняется при импорте, и спрячьте точку входа за защитой main, открыв остальное как функции.

Готово, когда: импорт файла не выполняет никакой работы, а его функции можно вызвать из теста.

Частые ошибки

  • Установка в системный интерпретатор — проекты становятся взаимно несовместимыми, и инструменты самой операционной системы могут сломаться; исправить: используйте виртуальное окружение для каждого проекта.
  • Запись зависимостей по памяти — сборка не воспроизводима ни для кого, кроме вас; исправить: записывайте версии и фиксируйте транзитивный набор.
  • Работа на уровне модуля — импорт файла её выполняет, а циклы дают запутанные сбои запуска; исправить: держите импорты без побочных эффектов и используйте защиту main.
  • Открытие каждого подмодуля наружу — внутренние изменения расходятся по всем, кто этот пакет вызывает; исправить: задайте небольшую публичную поверхность.
  • Отложенный импорт вместо разрыва цикла — структурная проблема остаётся; исправить: вынесите общую часть в отдельный модуль.

Что читать дальше