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

У меня проблемы с пониманием того, как python управляет imports.

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

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, который создает бесконечный цикл.

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
0
113
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 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']

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

Другие вопросы по теме