Довольно частой проблемой с которой сталкиваются новички в Python является проблема рекурсивного импорта. Именно о ней я и расскажу в этой заметке. Побудило меня на это расспросы моих же знакомых и друзей, которых я недавно пересадил на этот замечательный язык.
И так начнем.
Где и как проявляется проблема рекурсивного импорта?
Проблема проявляется при взаимном импорте двух модулей. Вот простой пример.
Есть модуль A, который импортирует модуль B:
Code: Python
import B
def test ():
print "Module A"
И есть модуль B, который импортирует A:
Code: Python
Если запустим модуль B, вывалимся со следующей ошибкой:
Code:
Traceback (most recent call last):
File "B.py", line 1, in <module>
import A
File "C:\Users\Prosper-H\Desktop\test\A.py", line 1, in <module>
import B
File "C:\Users\Prosper-H\Desktop\test\B.py", line 3, in <module>
A.test()
AttributeError: 'module' object has no attribute 'test'
В ошибке говорится, что модуль A не содержит функцию test. Однако, мы знаем, что она там должна быть? Так в чем же проблема?
А проблема заключается в концепции самого Python. Как вы наверняка знаете, в языке Python всё является объектами, в том числе и загружаемые модули. Поэтому при импорте модуля загружается не текст файла, как это происходит, например, в языке Си, а грубо говоря ссылка на сформированный объект, через которую можно обратиться к содержимому модуля.
Сам объект модуля может находиться в трех состояниях:- Не сформированном.
- Частично сформированном.
- Сформированном (необходимо выполнение всего содержимого модуля).
Если модуль импортируется в первый раз, то происходит создание объекта модуля, сохранение ссылки на объект в области видимости импортирующего модуля и выполнение содержимого импортируемого модуля.
Последующие импорты, когда-либо полностью сформированного модуля, возвращают только ссылку на его объект и не приводят к повторному выполнению его содержимого.
Если происходит импорт частично сформированного модуля, который до этого не импортировался импортирующим, то импортирующий сохраняет ссылку на объект и говорит такому модулю начать свое формирование с начала.
Если происходит импорт частично сформированного модуля, который уже импортировался импортирующим, то импортирующий сохраняет ссылку на частично сформированный модуль и продолжает свое выполнение.
Зная это давайте вернемся к нашим баранам.
А и Б сидели на трубе
В нашем примере происходит следующее:- При запуске модуля B, происходит импорт модуля А. (B - частично сформирован; А - не сформирован и помечен как загруженный в область видимости B)
- Модуль А сразу импортирует модуль B. Так как модуль B уже частично сформирован, то А заставляет его начать свое формирование заново. (B - частично сформирован и помечен как загруженный в А; А - частично сформирован и помечен как загруженный в B)
- Модуль B начинает свое переформирование и встречает импорт частично сформированного модуля А. Так как модуль А помечен, как уже загруженный в модуль B, модуль B просто получает ссылку на объект А и продолжает свое выполнение. (B - частично сформирован и получил ссылку на объект А; А - частично сформирован и помечен как загруженный в B)
- Модуль B вызывает А.test(). Так как модуль А является частично сформированным, а если точнее он ждет возврата из строки "import B", то функция "test" еще не была добавлена к объекту модуля А и поэтому, в момент вызова её из B она не существует.
Как решается данная проблема?
Решить данную проблему можно двумя путями:- Переработать конфликтный код так чтобы не было обращений к не инициализированными данным импортируемого модуля;
- Изначально писать свой код с учетом данной проблемы.
Других решений нет.
Чтобы код из нашего примера заработал, нужно поправить код модуля B:
Code: Python
import A
def run():
A.function()
if __name__ == "__main__":
run()
У нас довольно простой пример, поэтому решение тоже простое. В сложных случаях решение бывает не таким простым и порой требует значительной переработки.
На этом собственно всё!
Успехов =)
Prosper-H (r0 Crew)