Что произойдет, если два модуля импортируют друг друга?
Чтобы обобщить проблему, как насчет циклического импорта в Python?
Что произойдет, если два модуля импортируют друг друга?
Чтобы обобщить проблему, как насчет циклического импорта в Python?
Ответы:
В прошлом году на comp.lang.python было действительно хорошее обсуждение этого вопроса . Он довольно подробно отвечает на ваш вопрос.
Импорт довольно прост на самом деле. Просто запомните следующее:
'import' и 'from xxx import yyy' являются исполняемыми операторами. Они выполняются, когда запущенная программа достигает этой строки.
Если модуль отсутствует в sys.modules, то при импорте создается новая запись модуля в sys.modules, а затем выполняется код в модуле. Он не возвращает управление вызывающему модулю, пока выполнение не завершится.
Если модуль существует в sys.modules, то импорт просто возвращает этот модуль независимо от того, завершил ли он выполнение. По этой причине циклический импорт может возвращать модули, которые кажутся частично пустыми.
Наконец, исполняемый скрипт выполняется в модуле с именем __main__, импорт скрипта под его собственным именем создаст новый модуль, не связанный с __main__.
Возьмите это вместе, и вы не должны удивляться при импорте модулей.
Если вы делаете import fooвнутри barи import barвнутри foo, это будет работать нормально. К тому времени, когда что-то действительно запустится, оба модуля будут полностью загружены и будут иметь ссылки друг на друга.
Проблема в том, когда вместо этого вы делаете from foo import abcи from bar import xyz. Потому что теперь каждый модуль требует, чтобы другой модуль уже был импортирован (чтобы существующее имя импортировалось), прежде чем его можно будет импортировать.
from foo import *и from bar import *будет работать нормально.
from x import y, и все же получает круговую ошибку импорта
importинструкции. Таким образом, он не выдаст ошибку, но вы можете не получить все ожидаемые переменные.
from foo import *и from bar import *, все выполнено в fooнаходится в фазе инициализации bar, а фактические функции до barсих пор не определена ...
Циклический импорт завершается, но вы должны быть осторожны, чтобы не использовать циклически импортированные модули во время инициализации модуля.
Рассмотрим следующие файлы:
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.
__name__вместо 'a'. Вначале я был совершенно сбит с толку, почему файл будет выполнен дважды.
Как и другие ответы описывают этот шаблон приемлем в Python:
def dostuff(self):
from foo import bar
...
Что позволит избежать выполнения оператора импорта, когда файл импортируется другими модулями. Только если есть логическая циклическая зависимость, это потерпит неудачу.
Большинство циклических импортов на самом деле не являются логическими циклическими импортами, а скорее вызывают 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он вызван, потому что он уже импортирован, он извлечет его из кэша импорта.
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
import barв foo.pyдо конца
barи то, и fooдругое нужно использовать gX, самое «чистое» решение - это вставить gXдругой модуль, иметь оба fooи barимпортировать этот модуль. (самый чистый в том смысле, что нет никаких скрытых семантических зависимостей.)
barне могу найти gXв foo. круговой импорт хорош сам по себе, но он gXне определен при импорте.
Модуль a.py:
import b
print("This is from module a")
Модуль б.пи
import a
print("This is from module b")
Запуск «Модуль А» будет выводить:
>>>
'This is from module a'
'This is from module b'
'This is from module a'
>>>
Он вывел эти 3 строки, в то время как должен был вывести бесконечность из-за циклического импорта. То, что происходит построчно при запуске «Модуля А», указано здесь:
import b. поэтому он посетит модуль Bimport a. поэтому он посетит модуль Аimport bно обратите внимание, что эта строка больше не будет выполняться , потому что каждый файл в python выполняет строку импорта только один раз, не имеет значения, где и когда она выполняется. поэтому он перейдет к следующей строке и напечатает "This is from module a"."This is from module b""This is from module a"и программа будет завершена.Я полностью согласен с ответом питона здесь. Но я наткнулся на некоторый код, который имел недостатки при круговом импорте и вызывал проблемы при попытке добавить модульные тесты. Таким образом, чтобы быстро исправить это без изменения всего, вы можете решить проблему, выполнив динамический импорт.
# 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
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.
Круговой импорт может сбивать с толку, потому что импорт делает две вещи:
Первое выполняется только один раз, а второе - в каждом операторе импорта. Круговой импорт создает ситуацию, когда импортирующий модуль использует импортированный с частично выполненным кодом. В результате он не увидит объекты, созданные после оператора импорта. Ниже пример кода демонстрирует это.
Круговой импорт не является последним злом, которого следует избегать любой ценой. В некоторых средах, таких как 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
Я решил проблему следующим образом, и она работает без ошибок. Рассмотрим два файла a.pyи b.py.
Я добавил это, a.pyи это сработало.
if __name__ == "__main__":
main ()
import b
y = 2
def main():
print ("a out")
print (b.x)
if __name__ == "__main__":
main ()
import a
print ("b out")
x = 3 + a.y
Я получаю вывод
>>> b out
>>> a out
>>> 5
Хорошо, я думаю, у меня есть довольно крутое решение. Допустим, у вас есть файл 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, и, таким образом, вы исключаете любой циклический импорт.
Например:
class A(object):
def __init__(self, name):
self.name = name
CLASS = A("me")
import b
go = B(6)
go.dostuff
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."
Вуаля.
from a import CLASSна самом деле не пропускает выполнение всего кода в a.py. Вот что действительно происходит: (1) Весь код в a.py запускается как специальный модуль «__main__». (2) При import bзапуске запускается код верхнего уровня в b.py (определяющий класс B), а затем управление возвращается к «__main__». (3) "__main__" в конечном итоге передает управление go.dostuff(). (4) когда приходит dostuff () import a, он снова запускает весь код в a.py , на этот раз как модуль «a»; затем он импортирует объект CLASS из нового модуля "a". Так что на самом деле, это будет работать одинаково хорошо, если вы используете import aгде-нибудь в b.py.