Вопрос-Ответ

What happens when using mutual or circular (cyclic) imports?

Что происходит при использовании взаимного или циклического (cyclic) импорта?

Что происходит в Python, когда два модуля пытаются import друг друга? В более общем плане, что происходит, если несколько модулей пытаются import в цикле?


Смотрите также Что я могу сделать с "ImportError: не удается импортировать имя X" или "AttributeError: ... (скорее всего, из-за циклического импорта)"? о распространенной проблеме, которая может возникнуть, и советы о том, как переписать код, чтобы избежать такого импорта. Смотрите, почему циклический импорт, казалось бы, работает дальше в стеке вызовов, но затем вызывает ImportError еще ниже? технические подробности о том, почему и как возникает проблема.

Переведено автоматически
Ответ 1

Если вы сделаете import foo (внутри bar.py) и import bar (внутри foo.py), это будет работать нормально. К тому времени, когда что-либо действительно запустится, оба модуля будут полностью загружены и будут иметь ссылки друг на друга.

Проблема заключается в том, что вместо этого вы делаете from foo import abc (внутри bar.py) и from bar import xyz (внутри foo.py). Потому что теперь каждый модуль требует, чтобы другой модуль уже был импортирован (чтобы имя, которое мы импортируем, существовало), прежде чем его можно будет импортировать.

Примеры рабочего циклического импорта в Python 2 и Python 3

В статье Когда циклический импорт Python является фатальным? приведены четыре примера, когда циклический импорт по объясненной выше причине не является фатальным.

Начало модуля; нет из; Только Python 2

# lib/foo.py                         # lib/bar.py
import bar import foo

def abc(): def xyz():
print(bar.xyz.__name__) print(foo.abc.__name__)

Начало модуля; из ok; относительное ok; Только для Python 3

# lib/foo.py                         # lib/bar.py
from . import bar from . import foo

def abc(): def xyz():
print(bar.xyz.__name__) print(abc.__name__)

Начало модуля; нет from; нет relative

# lib/foo.py                         # lib/bar.py
import lib.bar import lib.foo

def abc(): def xyz():
print(lib.bar.xyz.__name__) print(lib.foo.abc.__name__)

Внизу модуля; импортировать атрибут, а не модуль; из okay

# lib/foo.py                         # lib/bar.py
def abc(): def xyz():
print(xyz.__name__) print(abc.__name__)


from .bar import xyz from .foo import abc

Начало функции; из okay

# lib/foo.py                         # lib/bar.py
def abc(): def xyz():
from . import bar from . import foo
print(bar.xyz.__name__) print(foo.abc.__name__)

Дополнительные примеры

В приведенной выше статье не обсуждается звездообразный импорт.

Ответ 2

В прошлом году на comp.lang.python было действительно хорошее обсуждение этого вопроса. Он довольно подробно отвечает на ваш вопрос.


Импорт на самом деле довольно прост. Просто помните следующее:


'import' и 'from xxx import yyy' являются исполняемыми операторами. Они выполняются, когда запущенная программа достигает этой строки.


Если модуля нет в sys.modules, то при импорте создается новая запись модуля в sys.modules, а затем выполняется код в модуле. Он не возвращает управление вызывающему модулю до завершения выполнения.


Если модуль действительно существует в sys.modules, то импорт просто возвращает этот модуль независимо от того, завершил он выполнение или нет. Именно по этой причине циклический импорт может возвращать модули, которые кажутся частично пустыми.


Наконец, выполняющийся скрипт выполняется в модуле с именем __main__, импорт скрипта под его собственным именем создаст новый модуль, не связанный с __main__.


Соберите все это вместе, и вы не должны столкнуться с какими-либо сюрпризами при импорте модулей.


Ответ 3

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

Рассмотрим следующие файлы:

a.py:

print "a in"
import sys
print "b imported: %s" % ("b" in sys.modules, )
import b
print "a out"

b.py:

print "b in"
import a
print "b out"
x = 3

При выполнении a.py вы получите следующее:

$ python a.py
a in
b imported: False
b in
a in
b imported: True
a out
b out
a out

При втором импорте b.py (во втором a in) интерпретатор Python не импортирует b снова, потому что он уже существует в модуле dict .

Если вы попытаетесь получить доступ b.x из a во время инициализации модуля, вы получите AttributeError.

Добавьте следующую строку в a.py:

print b.x

Тогда результат будет следующим:

$ python a.py
a in
b imported: False
b in
a in
b imported: True
a out
Traceback (most recent call last):
File "a.py", line 4, in <module>
import b
File "/home/shlomme/tmp/x/b.py", line 2, in <module>
import a
File "/home/shlomme/tmp/x/a.py", line 7, in <module>
print b.x
AttributeError: 'module' object has no attribute 'x'

Это связано с тем, что модули выполняются при импорте и на момент b.x обращения к строке x = 3 еще не выполнена, что произойдет только после b out.

Ответ 4

Как описано в других ответах, этот шаблон приемлем в python:

def dostuff(self):
from foo import bar
...

Это позволит избежать выполнения инструкции import при импорте файла другими модулями. Сбой произойдет только при наличии логической циклической зависимости.

Большинство циклических импортов на самом деле не являются логическими циклическими импортами, а скорее вызывают ImportError ошибки из-за способа import() вычисления инструкций верхнего уровня всего файла при вызове.

Этого ImportErrors почти всегда можно избежать, если вы хотите, чтобы ваш импорт был сверху:

Рассмотрим этот циклический импорт:

Приложение A

# profiles/serializers.py

from images.serializers import SimplifiedImageSerializer

class SimplifiedProfileSerializer(serializers.Serializer):
name = serializers.CharField()

class ProfileSerializer(SimplifiedProfileSerializer):
recent_images = SimplifiedImageSerializer(many=True)

Приложение B

# images/serializers.py

from profiles.serializers import SimplifiedProfileSerializer

class SimplifiedImageSerializer(serializers.Serializer):
title = serializers.CharField()

class ImageSerializer(SimplifiedImageSerializer):
profile = SimplifiedProfileSerializer()

Из превосходного доклада Дэвида Бизли Модули и пакеты: живи и дай умереть! - PyCon 2015, 1:54:00 вот способ справиться с циклическим импортом в python:

try:
from images.serializers import SimplifiedImageSerializer
except ImportError:
import sys
SimplifiedImageSerializer = sys.modules[__package__ + '.SimplifiedImageSerializer']

Это пытается импортировать, SimplifiedImageSerializer и если ImportError будет вызвано, поскольку оно уже импортировано, оно извлечет его из importcache.

PS: Вы должны прочитать весь этот пост голосом Дэвида Бизли.

2023-07-19 11:58 python