Зацикливание импорта в Python

06.11.2018 0 Автор admin

Довольно частой проблемой с которой сталкиваются новички в Python является проблема рекурсивного импорта. Именно о ней я и расскажу в этой заметке. Побудило меня на это расспросы моих же знакомых и друзей, которых я недавно пересадил на этот замечательный язык.

И так начнем.

Где и как проявляется проблема рекурсивного импорта?

Проблема проявляется при взаимном импорте двух модулей. Вот простой пример.

Есть модуль A, который импортирует модуль B:

И есть модуль B, который импортирует A:

Если запустим модуль B, вывалимся со следующей ошибкой:

Code:

В ошибке говорится, что модуль A не содержит функцию test. Однако, мы знаем, что она там должна быть? Так в чем же проблема?

А проблема заключается в концепции самого Python. Как вы наверняка знаете, в языке Python всё является объектами, в том числе и загружаемые модули. Поэтому при импорте модуля загружается не текст файла, как это происходит, например, в языке Си, а грубо говоря ссылка на сформированный объект, через которую можно обратиться к содержимому модуля.

Сам объект модуля может находиться в трех состояниях:

  • Не сформированном.
  • Частично сформированном.
  • Сформированном (необходимо выполнение всего содержимого модуля).

Если модуль импортируется в первый раз, то происходит создание объекта модуля, сохранение ссылки на объект в области видимости импортирующего модуля и выполнение содержимого импортируемого модуля.

Последующие импорты, когда-либо полностью сформированного модуля, возвращают только ссылку на его объект и не приводят к повторному выполнению его содержимого.

Если происходит импорт частично сформированного модуля, который до этого не импортировался импортирующим, то импортирующий сохраняет ссылку на объект и говорит такому модулю начать свое формирование с начала.

Если происходит импорт частично сформированного модуля, который уже импортировался импортирующим, то импортирующий сохраняет ссылку на частично сформированный модуль и продолжает свое выполнение.

Зная это давайте вернемся к нашим баранам.

А и Б сидели на трубе

В нашем примере происходит следующее:

  • При запуске модуля B, происходит импорт модуля А. (B — частично сформирован; А — не сформирован и помечен как загруженный в область видимости B)
  • Модуль А сразу импортирует модуль B. Так как модуль B уже частично сформирован, то А заставляет его начать свое формирование заново. (B — частично сформирован и помечен как загруженный в А; А — частично сформирован и помечен как загруженный в B)
  • Модуль B начинает свое переформирование и встречает импорт частично сформированного модуля А. Так как модуль А помечен, как уже загруженный в модуль B, модуль B просто получает ссылку на объект А и продолжает свое выполнение. (B — частично сформирован и получил ссылку на объект А; А — частично сформирован и помечен как загруженный в B)
  • Модуль B вызывает А.test(). Так как модуль А является частично сформированным, а если точнее он ждет возврата из строки «import B», то функция «test» еще не была добавлена к объекту модуля А и поэтому, в момент вызова её из B она не существует.

Как решается данная проблема?

Решить данную проблему можно двумя путями:

  • Переработать конфликтный код так чтобы не было обращений к не инициализированными данным импортируемого модуля;
  • Изначально писать свой код с учетом данной проблемы.

Других решений нет.

Чтобы код из нашего примера заработал, нужно поправить код модуля B:

На этом собственно всё!

Успехов =)

Взято отсюда.

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.