R0 CREW

Объяснение проблемы рекурсивного импорта в Python

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

И так начнем.

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

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

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

import B

def test ():
print "Module A"

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

import A

A.test()

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

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:

import A

def run():
A.function()


if __name__ == "__main__":
run()

У нас довольно простой пример, поэтому решение тоже простое. В сложных случаях решение бывает не таким простым и порой требует значительной переработки.

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

Успехов =)

Prosper-H (r0 Crew)

А почему вы выбрали язык программирования Python?

А какие альтернативы?

Ассемблер