+ Reply to Thread
Results 1 to 4 of 4

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

  1. #1
    Prosper-H's Avatar

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

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

    И так начнем.


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

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

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

    Code: Python
    import B

    def test ():
    print "Module A"

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

    Code: Python
    import A

    A.test()

    Если запустим модуль 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)
    Дорогу осилит идущий. (К. Касперски)

    Двери есть везде. Просто нужно знать, как в них войти. ("Хроники Амбера", персонаж: Корвин)

  2. 2 пользователя(ей) сказали cпасибо:
    dukeBarman (08-01-2016) ximera (06-01-2016)
  3. #2

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

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

  4. #3
    ximera's Avatar

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

    Quote Originally Posted by kaufen View Post
    А почему вы выбрали язык программирования Python?
    А какие альтернативы?
    Чтобы избегать ошибок, надо набираться опыта; чтобы набираться опыта, надо делать ошибки. © Лоренс Питер

    Неизбежное прими достойно. © Сенека Луций Анней

    Господи... храни сумасшедших. © Сумасшедший Фрэнки

  5. #4
    500mhz's Avatar

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

    Ассемблер

+ Reply to Thread

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
All times are GMT. The time now is 21:00
vBulletin® Copyright ©2000 - 2019
www.reverse4you.org