Круговой (или циклический) импорт в Python



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



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

3763   9  

9 ответов:

была действительно хорошая дискуссия по этому поводу в комп.ленг.питон в прошлом году. Он отвечает на ваш вопрос довольно тщательно.

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

'и' от ХХХ импорт ыыы являются исполняемых операторов. Они исполняют когда запущенная программа достигает этой строки.

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

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

наконец, выполняемый скрипт выполняется в модуле с именем _ _ main__, импортируя скрипт под свой собственный имя создаст новый модуль, не связанный с __главный.__

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

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

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

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

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

а.пы:

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

б.пы:

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 опять же, потому что он уже существует в модуль дикт.

если вы попытаетесь получить доступ к 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.

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

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

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

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

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

рассмотрим этот круговой импорт:

Приложение

# profiles/serializers.py

from images.serializers import SimplifiedImageSerializer

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

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

Приложение Б

# 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: вы должны прочитать весь этот пост в голосе Дэвида Бизли.

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

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

Я полностью согласен с ответом 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")

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

Ура!

модуль 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 строки, в то время как он должен был выводить infinitival из-за кругового импорта. Что происходит строка за строкой при запуске "модуля a", указано здесь:

  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" и программа будет закончена.

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

  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"

а.ру

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

хорошо, я думаю, что у меня есть довольно крутое решение. Допустим, у вас есть файл a и файл b. У вас есть def или class в файле b что вы хотите использовать в модуле a, но у вас есть что-то еще, либо 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."

вуаля.

Comments

    Ничего не найдено.