Зачем нужны «ленивые» (lazy) импортыКогда модуль импортируется, интерпретатор выполняет весь код на глобальном уровне этого модуля, включая все его собственные импорты и инициализации. В больших приложениях и тестовых наборах это может заметно замедлять запуск и фазу сбора тестов. Поэтому идея «ленивого импорта» — откладывать импорт «тяжёлых» зависимостей до момента, когда они действительно понадобятся — помогает улучшить отзывчивость приложения и сократить время тестирования.
Переносим import внутрь функцииСамый очевидный и безопасный способ сделать импорт ленивым — переместить
import из глобальной области видимости внутрь функции или метода, где ресурс реально используется. При таком подходе импорт произойдёт только при первом вызове этой функции (и далее кешируется в
sys.modules, поэтому реальной «повторной» загрузки не происходит). Это даёт быстрый выигрыш для модулей, которые редко используются или инициализируют тяжёлые зависимости:
def do_heavy_task():
import heavy_lib
heavy_lib.run()
Плюсы: простота. Минусы: если импорт нужен во многих местах, придётся либо дублировать
import (что допустимо), либо устанавливать глобальную переменную после первого импорта.
Вариант с importlib — когда нужно контролировать пространство имёнЕсли хочется более явного контроля (например, избежать появления имени в локальной области каждой функции), можно использовать
importlib.import_module() и присваивать результат в переменную (глобальную или локальную). Это зачастую полезно при динамическом импорте по имени строки:
Пример:
from importlib import import_module
def use_feature():
mod = import_module("heavy_lib")
mod.do()
Как найти «тяжёлые» импорты — инструмент `python -X importtimeПрежде чем делать импорты ленивыми, полезно понять, что именно тормозит загрузку. Для этого есть встроенная опция:
python -X importtime your_program.py — она выводит дерево импорта с временами, позволяя увидеть самые затратные узлы. Это особенно полезно при оптимизации большого проекта или ускорении фазы сбора тестов.
Особая зона внимания — pytest и фаза collectionPytest во время collection импортирует все тестовые файлы — следовательно, импорты в глобальной области тестов будут исполнены на этапе collection, даже если сам тест не будет запущен. Это распространённый источник задержек в больших тестовых наборах. Решение — переносить импорты внутрь тестовых функций, использовать
importlib внутри тестов.
«Глобальный» трюкЕсли модуль содержит множество функций, которые все используют одну и ту же тяжёлую библиотеку, имеет смысл импортировать её при первом нужном вызове и сохранить в глобальной переменной модуля (через `global`).
Короткая иллюстрация:
# module.py
heavy = None
def first_use():
global heavy
if heavy is None:
import heavy_lib
heavy = heavy_lib
heavy.do()
Когда ленивые импорты — плохая идея🔘 Если импорт жизненно важен для модуля и должен бросать ошибки во время старта (fail fast), откладывание импорта может скрыть проблему до момента выполнения, что усложнит отладку.
🔘 Когда импорт идёт с побочными эффектами, которые вы ожидаете увидеть при импортировании модуля — откладывая импорт, вы меняете поведение.
#основы
@zen_of_python