Как Python ведёт себя при повторном импорте одного и того же модуля в рамках одного процесса?
При первом импорте Python создаёт объект модуля, выполняет его верхнеуровневый код и сохраняет объект в кэше sys.modules. Повторный импорт того же модуля обычно возвращает уже существующий объект без повторного выполнения его верхнеуровневого кода.
Модульная система Python предназначена для разделения программы на повторно используемые пространства имён. Кэширование модулей позволяет не выполнять код инициализации заново при каждом импорте и сохранять единое состояние модуля внутри процесса.
Такой подход также уменьшает стоимость повторных импортов: после первой загрузки Python может использовать уже подготовленный объект вместо повторного поиска, создания и выполнения модуля.
Если разработчик ожидает, что повторный импорт заново прочитает настройки, сбросит глобальные переменные или выполнит побочные действия, программа поведёт себя неожиданно. Состояние модуля останется общим для всех импортировавших его частей процесса.
Это особенно важно для конфигурации, реестров, кэшей, подключения к ресурсам и тестов. Ошибка в предположении о повторной инициализации может привести к утечке состояния между тестами или к использованию устаревших данных.
При загрузке Python связывает имя модуля с записью в sys.modules. Во время первой загрузки создаётся объект модуля, он помещается в этот кэш, после чего выполняется код модуля. Помещение объекта в кэш до полного завершения выполнения необходимо, в частности, для корректной обработки циклических импортов.
Последующий импорт проверяет sys.modules. Если нужная запись уже есть, Python использует тот же объект и не выполняет верхнеуровневый код повторно. Поэтому два места программы обычно видят одни и те же глобальные переменные модуля.
Разные формы импорта меняют способ привязки имён в текущем пространстве имён, но не отменяют кэширование модуля. Удаление записи из sys.modules или загрузка одного файла под разными именами может привести к созданию нескольких объектов модуля, однако это уже нестандартный и потенциально опасный сценарий.
importlib.reload — отдельная операция: она повторно выполняет код модуля в существующем объекте модуля. Ссылки на отдельные объекты, полученные ранее через импорт имён, автоматически не обновляются, поэтому перезагрузка не является надёжным способом сброса всего состояния приложения.
В сервисе модуль конфигурации читает файл при импорте и сохраняет результат в глобальной переменной. После изменения файла разработчик ожидает, что повторный импорт обновит настройки, но сервис продолжает использовать прежний объект модуля из sys.modules.
Возможны три решения. Повторный обычный импорт не подходит: он прост, но не обновляет состояние. Принудительная перезагрузка через importlib.reload обновляет код и часть данных, однако может оставить старые ссылки и побочные эффекты. Удаление записи из sys.modules ещё рискованнее, поскольку способно создать несколько экземпляров состояния.
Предпочтительное решение — вынести чтение конфигурации в явную функцию или фабрику и вызывать её тогда, когда требуется обновление. Это делает момент загрузки очевидным, упрощает тестирование и не связывает жизненный цикл конфигурации с механизмом импорта.
Вопрос: Гарантирует ли наличие модуля в sys.modules успешное завершение его импорта?
Ответ: Нет. Запись обычно помещается в sys.modules до выполнения тела модуля, чтобы поддерживать циклические импорты. Если выполнение завершается исключением, Python обычно удаляет незавершённую запись, но уже созданные побочные эффекты и изменения во внешних объектах могут сохраниться.
Вопрос: Что изменится, если один и тот же файл импортировать под двумя разными именами?
Ответ: Python ориентируется на имя ключа в sys.modules, а не только на физический путь. Если один файл оказался загружен под разными именами, могут появиться два объекта модуля с независимыми глобальными состояниями и повторным выполнением кода. Это может нарушить проверки идентичности классов, регистрацию обработчиков и работу синглтонов, поэтому согласованные имена импорта критически важны.
Вопрос: Почему перезагрузка модуля не обновляет уже импортированный класс в другом модуле?
Ответ: При перезагрузке код модуля выполняется заново, и объявление класса создаёт новый объект класса. Ссылка, ранее скопированная в другой модуль, продолжает указывать на старый объект класса. Поэтому экземпляры старого класса не обязательно проходят проверки через новый класс, а существующие объекты не меняют свою принадлежность автоматически.