Круговой (или циклический) импорт в Python - PullRequest
292 голосов
/ 13 апреля 2009

Что будет, если два модуля импортируют друг друга?

Чтобы обобщить проблему, как насчет циклического импорта в Python?

Ответы [ 11 ]

246 голосов
/ 14 апреля 2009

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

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

245 голосов
/ 13 апреля 2009

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

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

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

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

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

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

Возьмите эту партию вместе, и при импорте вы не получите никаких сюрпризов. модули.

95 голосов
/ 13 апреля 2009

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

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

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.

25 голосов
/ 05 ноября 2015

Как и другие ответы описывают этот шаблон приемлем в Python:

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

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

Большинство циклических импортов на самом деле не являются логическими циклическими импортами, а скорее вызывают ошибки 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 поднято, потому что оно уже импортировано, оно извлечет его из кэша импорта.

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

8 голосов
/ 14 апреля 2009

У меня есть пример, который поразил меня!

foo.py

import bar

class gX(object):
    g = 10

bar.py

from foo import gX

o = gX()

main.py

import foo
import bar

print "all done"

В командной строке: $ python main.py

Traceback (most recent call last):
  File "m.py", line 1, in <module>
    import foo
  File "/home/xolve/foo.py", line 1, in <module>
    import bar
  File "/home/xolve/bar.py", line 1, in <module>
    from foo import gX
ImportError: cannot import name gX
4 голосов
/ 06 июля 2018

Модуль a.py:

import b
print("This is from module a")

Модуль b.py

import a
print("This is from module b")

Запуск «Модуля А» выдаст:

>>> 
'This is from module a'
'This is from module b'
'This is from module a'
>>> 

Он выводил эти 3 строки, в то время как предполагалось, что он выводит бесконечность из-за циклического импорта. То, что происходит построчно при запуске «Модуля А», указано здесь:

  1. Первая строка - import b. поэтому он посетит модуль b
  2. Первая строка в модуле b - import a. поэтому он посетит модуль
  3. Первая строка в модуле a - import b, но обратите внимание, что эта строка больше не будет выполняться , потому что каждый файл в python выполняет строку импорта только один раз , не имеет значения, где и когда он выполняется. поэтому он перейдет к следующей строке и напечатает "This is from module a".
  4. После завершения посещения всего модуля a из модуля b мы все еще находимся в модуле b. поэтому следующая строка будет печатать "This is from module b"
  5. Строки модуля b выполнены полностью. поэтому мы вернемся к модулю a, где мы запустили модуль b.
  6. строка импорта b уже выполнена и больше не будет выполняться. следующая строка напечатает "This is from module a" и программа будет завершена.
4 голосов
/ 02 марта 2017

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

# Hack to import something without circular import issue
def load_module(name):
    """Load module using imp.find_module"""
    names = name.split(".")
    path = None
    for name in names:
        f, path, info = imp.find_module(name, path)
        path = [path]
    return imp.load_module(name, f, path[0], info)
constants = load_module("app.constants")

Опять же, это не постоянное исправление, но может помочь кому-то, кто хочет исправить ошибку импорта, не изменяя слишком много кода.

Ура!

1 голос
/ 09 октября 2018

Я решил проблему следующим образом, и она работает без ошибок. Рассмотрим два файла a.py и b.py.

Я добавил это к a.py, и это сработало.

if __name__ == "__main__":
        main ()

a.py:

import b
y = 2
def main():
    print ("a out")
    print (b.x)

if __name__ == "__main__":
    main ()

b.py:

import a
print ("b out")
x = 3 + a.y

Я получаю вывод

>>> b out 
>>> a out 
>>> 5
1 голос
/ 30 июня 2018

Круговой импорт может сбивать с толку, потому что импорт делает две вещи:

  1. выполняет импортированный код модуля
  2. добавляет импортированный модуль в глобальную таблицу символов модуля

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

Круговой импорт - не абсолютное зло, которого следует избегать любой ценой. В некоторых средах, таких как Flask, они вполне естественны, и настройка кода для их устранения не делает код лучше

main.py

print 'import b'
import b
print 'a in globals() {}'.format('a' in globals())
print 'import a'
import a
print 'a in globals() {}'.format('a' in globals())
if __name__ == '__main__':
    print 'imports done'
    print 'b has y {}, a is b.a {}'.format(hasattr(b, 'y'), a is b.a)

b.by

print "b in, __name__ = {}".format(__name__)
x = 3
print 'b imports a'
import a
y = 5
print "b out"

a.py

print 'a in, __name__ = {}'.format(__name__)
print 'a imports b'
import b
print 'b has x {}'.format(hasattr(b, 'x'))
print 'b has y {}'.format(hasattr(b, 'y'))
print "a out"

вывод python main.py с комментариями

import b
b in, __name__ = b    # b code execution started
b imports a
a in, __name__ = a    # a code execution started
a imports b           # b code execution is already in progress
b has x True
b has y False         # b defines y after a import,
a out
b out
a in globals() False  # import only adds a to main global symbol table 
import a
a in globals() True
imports done
b has y True, a is b.a True # all b objects are available
0 голосов
/ 12 октября 2018

Это может быть другое решение, сработало для меня.

def MandrillEmailOrderSerializer():
from sastaticketpk.apps.flights.api.v1.serializers import MandrillEmailOrderSerializer
return MandrillEmailOrderSerializer

email_data = MandrillEmailOrderSerializer()(order.booking).data
...