Циклическая зависимость — когда она завершается?

#python #python-3.x #import

#python #python-3.x #импорт

Вопрос:

Мне трудно понять, как python управляет import s.

Допустим, у меня следующая структура приложения:

 application/
- application.py
- model/
-- __init__.py
-- user.py
 

Допустим, что application.py файл импортирует модуль модели после создания базы данных следующим образом:

 db = SQLAlchemy(application) 
import model
 

Давайте также скажем model , что модуль импортирует user.py файл следующим образом:

 import user
 

Наконец, предположим, что user.py файл импортирует db экземпляр из application.py файла следующим образом:

 from application import db
 

Мне это кажется циклической зависимостью, поскольку application.py файл косвенно требует user.py файла, но user.py файл требует db экземпляра из application.py файла.

Я знаю, что этот код работает так, как я его тестировал, но может кто-нибудь точно объяснить, как Python обрабатывает это, и когда он завершает циклический цикл.

Подводя итог проблеме, при user.py импорте файла db из application.py файла мне кажется, что он также вызывает модуль импорта model , который создает бесконечный цикл.

Ответ №1:

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

В mymod1.py

 import sys
print("1. from mymod1:", [x for x in sys.modules if x.startswith("mymod")])
import mymod2
print("2. from mymod1:", [x for x in sys.modules if x.startswith("mymod")])
 

В mymod2.py

 import sys
print("1. from mymod2:", [x for x in sys.modules if x.startswith("mymod")])
import mymod3
print("2. from mymod2:", [x for x in sys.modules if x.startswith("mymod")])
 

В mymod3.py

 import sys
print("1. from mymod3:", [x for x in sys.modules if x.startswith("mymod")])
import mymod1
print("2. from mymod3:", [x for x in sys.modules if x.startswith("mymod")])
 

Теперь у нас есть цикл mymod1 -> mymod2 -> mymod3 -> mymod1 . Введите REPL и посмотрите, что произойдет:

 >>> import mymod1
1. from mymod1: ['mymod1']  # before mymod1 imported mymod2. note mymod1 is already there!
1. from mymod2: ['mymod1', 'mymod2']  # in mymod2 now, before importing mymod3
1. from mymod3: ['mymod1', 'mymod2', 'mymod3']  # before mymod3 imports mymod1
2. from mymod3: ['mymod1', 'mymod2', 'mymod3']  # mymod3 exit
2. from mymod2: ['mymod1', 'mymod2', 'mymod3']  # mymod2 exit
2. from mymod1: ['mymod1', 'mymod3', 'mymod2']  # mymod1 exit
 

Ключевым моментом здесь является то, что сам экземпляр модуля уже присутствует sys.modules до того, как он завершил выполнение. Это означает, что его можно импортировать снова, и он вернет существующий объект без необходимости повторного выполнения всего кода на уровне модуля.

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

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

 # mymod.py
import sys
print("var" in vars(sys.modules["mymod"]))
var = "I'm a name in mymod namespace"
print("var" in vars(sys.modules["mymod"]))
 

Импорт mymod приведет к печати False , а затем True само пространство имен модуля все еще изменяется, поскольку импорт находится в процессе выполнения.

Проницательный читатель, возможно, заметил это mymod2 и mymod3 поменял местами в выводе:

 2. from mymod2: ['mymod1', 'mymod2', 'mymod3']
2. from mymod1: ['mymod1', 'mymod3', 'mymod2']
                           |_____ wtf? _____|
 

На самом деле это не случайно! Механизм импорта в качестве последнего шага при загрузке модуля извлекает фактический модуль sys.modules . Если вы снова проверите в REPL, mymod1 теперь будет последним.

 >>> import mymod1
...
>>> [x for x in sys.modules if x.startswith("mymod")]
['mymod3', 'mymod2', 'mymod1']
 

Я не буду описывать, почему система импорта делает это, потому что на самом деле это не имеет отношения к вопросу, но пользователи, заинтересованные в том, чтобы узнать причины, должны увидеть это сообщение в списке рассылки от Guido.