Пакеты и импорты Python: __init__.py и поиск модулей
Импорт в Python не вставляет текст одного файла в другой. Интерпретатор находит модуль по полному имени, создаёт объект модуля, помещает его в sys.modules до выполнения…
Импорт в Python не вставляет текст одного файла в другой. Интерпретатор находит модуль по полному имени, создаёт объект модуля, помещает его в sys.modules до выполнения верхнеуровневого кода, а затем заполняет пространство имён. Понимание этого процесса помогает проектировать пакеты и отличать ошибку поиска от циклической зависимости или отсутствующего имени.
Модуль, пакет и __init__.py
Файл prices.py является модулем. Каталог shop с файлом __init__.py — обычный пакет, внутри которого модуль имеет полное имя shop.prices. При первом импорте пакета выполняется shop/__init__.py; поэтому в нём лучше оставлять лёгкую и предсказуемую инициализацию, а не сетевые запросы или долгие вычисления.
Python поддерживает и namespace packages без __init__.py, но это отдельный механизм для распределённого пакета. В обычном учебном или прикладном проекте явный файл делает границу пакета заметной и позволяет управлять его публичным интерфейсом.
Абсолютные и относительные импорты
Абсолютный импорт начинается с имени верхнего пакета: from shop.prices import total. Относительный работает внутри пакета: from .prices import total; одна точка означает текущий пакет. Относительный импорт нельзя правильно понять без значения __package__, поэтому прямой запуск внутреннего файла часто ломается.
Создайте структуру:
demo/
├── run_demo.py
└── shop/
├── __init__.py
└── prices.pyСодержимое shop/prices.py:
def total(values):
return sum(values)Содержимое shop/__init__.py и run_demo.py соответственно:
from .prices import totalfrom shop import total
print(total([40, 60, 50]))Из каталога demo команда python run_demo.py печатает 150.
Где Python ищет импорт
Поиск выполняют finders импортной системы, используя в том числе записи sys.path. Там обычно есть каталог запуска, пути стандартной библиотеки, site-packages активного окружения и настроенные дополнительные записи. Python не ищет произвольно по всему диску. Текущий каталог и способ запуска могут менять первую доступную точку поиска, поэтому код, работающий только из одной папки, ещё не является корректно установленным пакетом.
После загрузки модуль кэшируется в sys.modules. Повторный import обычно возвращает тот же объект и не выполняет файл заново. Это не повод использовать импорт как скрытый механизм запуска действий: такой код становится зависимым от порядка импортов и сложнее тестируется.
Как отличить отсутствующий модуль от цикла импортов
ModuleNotFoundError: No module named 'shop' означает, что верхний пакет не найден в доступных местах. Выведите sys.executable, sys.path и проверьте importlib.util.find_spec("shop"). Не лечите проблему случайным sys.path.append в каждом файле: установите проект или запускайте его от правильной корневой точки. Сообщение cannot import name ... from partially initialized module часто указывает на цикл: модуль A импортирует B, пока B снова пытается получить ещё не созданное имя из A. Вынесите общее определение в третий модуль или перенесите зависимости к ясному направлению.
Расширяем пакет без правки sys.path
Расширьте пакет shop модулем discounts.py, который импортирует total относительным импортом и применяет скидку. Экспортируйте только нужную функцию через __init__.py. Запустите внешний сценарий, затем выведите ключи sys.modules, начинающиеся с shop. Для самостоятельной проверки намеренно напишите неправильное полное имя, исследуйте find_spec и исправьте структуру без изменения sys.path из кода.
Карта работы импортной системы
- Полное имя модуля и доступные пути поиска определяют, какой файл будет загружен.
__init__.pyобозначает обычный пакет и выполняется при его первой загрузке.- Относительные импорты предназначены для связей внутри известного пакета.
- Циклический импорт — проблема направления зависимостей, а не сигнал добавить ещё один импорт.
Вопросы о модулях и публичном API
Выполняется ли модуль при каждом import?
Обычно нет. После первой успешной загрузки объект находится в sys.modules, и последующие импорты используют кэш. Явная перезагрузка существует, но редко является правильной архитектурой приложения.
Можно ли назвать свой файл json.py или typing.py?
Технически можно, но локальный файл может затенить одноимённый модуль стандартной библиотеки. Если импорт ведёт не туда, проверьте атрибут module.__file__ и переименуйте конфликтующий файл.
Что лучше: from package import name или import package?
Обе формы корректны. Явное имя модуля часто делает происхождение объекта заметнее, а реэкспорт из __init__.py удобен для небольшого стабильного публичного API.