Что произойдет, если два модуля импортируют друг друга?
Чтобы обобщить проблему, как насчет циклического импорта в 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.