Зачем в Python модулях создают файлы __init__.py
Что проверяют
Понимаете ли вы, как Python находит пакеты и что выполняется в момент импорта. Для data и ML это не теория: тяжёлый __init__.py замедляет каждый запуск скрипта, теста и DAG. Следом часто спрашивают, что будет, если файл убрать.
Как отвечать
- обычный пакет: каталог с
__init__.py. При первом импорте пакета или любого его подмодуля файл выполняется один раз, модуль кладётся вsys.modules; - namespace-пакет: с Python 3.3 (PEP 420) каталог без
__init__.pyтоже импортируется. Такой пакет может собираться из нескольких каталогов, но своего кода инициализации у него нет; - публичный интерфейс: в
__init__.pyподнимают нужные имена,from .features import build_features, и снаружи пишутfrom mypkg import build_features, не зная внутренней структуры; __all__: влияет только наfrom mypkg import *, обычный импорт по имени он не ограничивает;- ленивая загрузка: функция
__getattr__на уровне модуля (PEP 562) позволяет импортировать тяжёлый подмодуль только при первом обращении.
Почему файл создают, хотя импорт работает и без него: setuptools.find_packages() не видит каталоги без __init__.py, pytest в режиме импорта по умолчанию путает одноимённые тестовые файлы из разных каталогов, а явный пакет однозначно задаёт границы кода. В репозитории с ноутбуками, фичами и пайплайнами обычный пакет плюс pip install -e . избавляет от правки sys.path в каждом ноутбуке.
Где спотыкаются
Утверждают, что без __init__.py импортировать модули из каталога нельзя. Так было в Python 2, в Python 3 такой каталог станет namespace-пакетом.
Кладут в __init__.py тяжёлую работу: import torch, загрузку модели, подключение к базе, чтение конфига. В Airflow это особенно больно: планировщик регулярно парсит файлы DAG, и медленный импорт тормозит весь разбор.
Импортируют в __init__.py все подмодули подряд, а подмодули импортируют пакет обратно. Получается циклический импорт и ImportError на частично инициализированном модуле.