#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.