Циркулярный (или циклический) импорт в Python

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

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

Ответ 1

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

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

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

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

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

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

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

Ответ 2

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

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

Ответ 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
     ...

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

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

От David Beazleys отличный разговор Модули и пакеты: Live и Let Die! - 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: Вы должны прочитать весь этот пост в голосе Дэвида Бэйсли.

Ответ 5

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

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

Ответ 6

Модуль a.py:

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

Модуль b.py

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

Запустится "Модуль a":

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

Он выводит эти 3 строки, пока он должен выводить бесконечность из-за циклического импорта. Что происходит строка за строкой при запуске "Module a", приведено здесь:

  1. Первая строка - import b. поэтому он посетит модуль b
  2. Первая строка в модуле b - это import a. поэтому он посетит модуль 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. строка import b уже выполнена и больше не будет выполнена. следующая строка напечатает "This is from module a" и программа будет завершена.

Ответ 7

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

# 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")

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

Ура!

Ответ 8

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

  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

Ответ 9

Я решил проблему следующим образом, и она работает без ошибок. Рассмотрим два файла 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

Ответ 10

Здесь много хороших ответов. Хотя обычно есть быстрые решения проблемы, некоторые из которых кажутся более питонными, чем другие, если у вас есть возможность провести некоторый рефакторинг, другой подход - проанализировать организацию вашего кода и попытаться удалить циклическую зависимость. Например, вы можете найти следующее:

Файл a.py

from b import B

class A:
    @staticmethod
    def save_result(result):
        print('save the result')

    @staticmethod
    def do_something_a_ish(param):
        A.save_result(A.use_param_like_a_would(param))

    @staticmethod
    def do_something_related_to_b(param):
        B.do_something_b_ish(param)

Файл b.py

from a import A

class B:
    @staticmethod
    def do_something_b_ish(param):
        A.save_result(B.use_param_like_b_would(param))

В этом случае просто переместите один статический метод в отдельный файл, скажем c.py:

Файл c.py

def save_result(result):
    print('save the result')

позволит удалить метод save_result из A и, следовательно, разрешить удаление импорта A из a в b:

Восстановленный файл a.py

from b import B
from c import save_result

class A:
    @staticmethod
    def do_something_a_ish(param):
        A.save_result(A.use_param_like_a_would(param))

    @staticmethod
    def do_something_related_to_b(param):
        B.do_something_b_ish(param)

Восстановленный файл b.py

from c import save_result

class B:
    @staticmethod
    def do_something_b_ish(param):
        save_result(B.use_param_like_b_would(param))

В итоге, если у вас есть инструмент (например, pylint или PyCharm), который сообщает о методах, которые могут быть статическими, простое наложение на них декоратора staticmethod может оказаться не лучшим способом заставить замолчать предупреждение. Даже несмотря на то, что метод, похоже, связан с классом, было бы лучше выделить его, особенно если у вас есть несколько тесно связанных модулей, которым может потребоваться такая же функциональность, и вы намерены применять принципы DRY.

Ответ 11

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

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

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

Ответ 12

Хорошо, я думаю, что у меня довольно крутое решение. Скажем, у вас есть файл a и файл b. У вас есть def или class в файле b, который вы хотите использовать в модуле a, но у вас есть что-то еще, либо def, class, либо переменная из файла a что вам нужно в вашем определении или классе в файле b. Что вы можете сделать, в нижней части файла a, после вызова функции или класса в файле a, которая требуется в файле b, но перед вызовом функции или класса из файла b, который вам нужен для файла a, скажем import b Затем, и вот ключевая часть, во всех определениях или классах в файле b, которым требуется def или class из файла a (позвоните ему class), вы говорите from a import CLASS

Это работает, потому что вы можете импортировать файл b без выполнения Python каких-либо операторов импорта в файле b, и, таким образом, вы избегаете любых циклических импорта.

Например:

Файл a:

class A(object):

     def __init__(self, name):

         self.name = name

CLASS = A("me")

import b

go = B(6)

go.dostuff

Файл b:

class B(object):

     def __init__(self, number):

         self.number = number

     def dostuff(self):

         from a import CLASS

         print "Hello " + CLASS.name + ", " + str(number) + " is an interesting number."

Voila.