Что будет, если два модуля импортируют друг друга?
Чтобы обобщить проблему, как насчет циклического импорта в Python?
Что будет, если два модуля импортируют друг друга?
Чтобы обобщить проблему, как насчет циклического импорта в Python?
Это было действительно хорошее обсуждение на comp.lang.python в прошлом году. Он довольно подробно отвечает на ваш вопрос.
Импорт на самом деле довольно прост. Просто запомните следующее:
'import' и 'from xxx import yyy' являются исполняемыми операторами. Они выполняются, когда запущенная программа достигает этой строки.
Если модуля нет в sys.modules, то при импорте создается новая запись модуля в sys.modules, а затем выполняется код в модуле. Он не возвращает управление вызывающему модулю до завершения выполнения.
Если модуль действительно существует в sys.modules, тогда импорт просто возвращает этот модуль независимо от того, завершил он выполнение или нет. По этой причине циклический импорт может возвращать модули, которые кажутся частично пустыми.
Наконец, исполняемый скрипт запускается в модуле с именем __main__, импорт скрипта под его собственным именем создаст новый модуль, не связанный с __main__.
Возьмите все это вместе, и вы не получите никаких сюрпризов при импорте модулей.
Если вы сделаете import foo
(внутри bar.py
) и import bar
(внутри foo.py
), он будет работать нормально. К тому времени, когда что-либо действительно запустится, оба модуля будут полностью загружены и будут иметь ссылки друг на друга.
Проблема в том, что вместо этого вы делаете from foo import abc
(внутри bar.py
) и from bar import xyz
(внутри foo.py
). Потому что теперь каждый модуль требует, чтобы другой модуль уже был импортирован (чтобы имя, которое мы импортируем, существовало), прежде чем он может быть импортирован.
from foo import *
и from bar import *
тоже будут работать нормально.
- person Akavall; 12.05.2014
from x import y
, но по-прежнему получает ошибку циклического импорта
- person Greg Ennis; 30.06.2014
import
. Таким образом, это не приведет к ошибке, но вы не сможете получить все ожидаемые переменные.
- person augurar; 24.12.2016
from foo import *
и from bar import *
, все, что выполняется в foo
, находится в фазе инициализации bar
, а фактические функции в bar
еще не определены ...
- person Martian2049; 18.01.2017
Циклический импорт завершается, но вы должны быть осторожны, чтобы не использовать циклически импортированные модули во время инициализации модуля.
Рассмотрим следующие файлы:
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'
. Вначале я совершенно не понимал, почему файл запускается дважды.
- person Bergi; 18.02.2020
Как описывают другие ответы, этот шаблон приемлем в 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
поднимается, потому что он уже импортирован, он извлечет его из importcache.
PS: Вы должны прочитать весь этот пост голосом Дэвида Бизли.
module
вместо class
из моего эксперимента.
- person Willjay; 05.02.2021
У меня есть пример, который меня поразил!
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
в конец
- person warvariuc; 05.08.2013
bar
и foo
оба должны использовать gX
, «самым чистым» решением будет поместить gX
в другой модуль и заставить оба foo
и bar
импортировать этот модуль. (самый чистый в том смысле, что нет скрытых семантических зависимостей.)
- person Tim Wilder; 18.12.2013
bar
не может даже найти gX
в foo. циклический импорт хорош сам по себе, но просто gX
не определен при импорте.
- person Martian2049; 18.01.2017
Модуль a.py:
import b
print("This is from module a")
Модуль b.py
import a
print("This is from module b")
Запуск "Module a" выведет:
>>>
'This is from module a'
'This is from module b'
'This is from module a'
>>>
Он выводил эти 3 строки, в то время как должен был выводить бесконечность из-за циклического импорта. Что происходит построчно при запуске «Модуля А», перечислено здесь:
import b
. поэтому он посетит модуль bimport a
. поэтому он посетит модуль aimport b
, но обратите внимание, что эта строка больше не будет выполняться, потому что каждый файл в python выполняет строку импорта только один раз, это делает независимо от того, где и когда он выполняется. поэтому он перейдет к следующей строке и напечатает "This is from module a"
."This is from module b"
"This is from module a"
, и программа будет завершена.Здесь есть много отличных ответов. Хотя обычно есть быстрые решения проблемы, некоторые из которых кажутся более питоническими, чем другие, если у вас есть роскошь провести некоторый рефакторинг, другой подход - проанализировать организацию вашего кода и попытаться удалить циклическую зависимость. Вы можете обнаружить, например, что у вас есть:
Файл 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.
Я полностью согласен с ответом 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")
Опять же, это не постоянное исправление, но может помочь кому-то, кто хочет исправить ошибку импорта, не меняя слишком много кода.
Ваше здоровье!
Циклический импорт может сбивать с толку, потому что импорт делает две вещи:
Первое выполняется только один раз, а второе - при каждом операторе импорта. Циклический импорт создает ситуацию, когда импортирующий модуль использует импортированный с частично выполненным кодом. Как следствие, он не будет видеть объекты, созданные после оператора импорта. Ниже приведен пример кода, демонстрирующий это.
Циркулярный импорт - не последнее зло, которого следует избегать любой ценой. В некоторых фреймворках, таких как 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
Предположим, вы запускаете тестовый файл Python с именем request.py
. В request.py вы пишете
import request
так что это, скорее всего, циклический импорт.
Решение:
Просто измените свой тестовый файл на другое имя, например aaa.py
, отличное от request.py
.
Не используйте имена, которые уже используются другими библиотеками.
Я решил проблему следующим образом, и он работает без ошибок. Рассмотрим два файла 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
К моему удивлению, никто еще не упомянул циклический импорт, вызванный подсказками типов.
Если у вас есть циклический импорт только в результате подсказок типов, их можно аккуратно избежать.
Рассмотрим main.py
, который использует исключения из другого файла:
from src.exceptions import SpecificException
class Foo:
def __init__(self, attrib: int):
self.attrib = attrib
raise SpecificException(Foo(5))
И специальный класс исключений exceptions.py
:
from src.main import Foo
class SpecificException(Exception):
def __init__(self, cause: Foo):
self.cause = cause
def __str__(self):
return f'Expected 3 but got {self.cause.attrib}.'
Это тривиально повысит ImportError
, поскольку main.py
импортирует exception.py
и наоборот через Foo
и SpecificException
.
Поскольку Foo
требуется только в exceptions.py
во время проверки типа, мы можем безопасно сделать его импорт условным, используя константу TYPE_CHECKING
из typing. Константа равна только True
во время проверки типа, что позволяет нам условно импортировать Foo
и тем самым избежать ошибки циклического импорта.
В Python 3.6 с использованием прямых ссылок:
from typing import TYPE_CHECKING
if TYPE_CHECKING: # Only imports the below statements during type checking
from src.main import Foo
class SpecificException(Exception):
def __init__(self, cause: 'Foo'): # The quotes make Foo a forward reference
self.cause = cause
def __str__(self):
return f'Expected 3 but got {self.cause.attrib}.'
В Python 3.7+ отложенная оценка аннотаций (введена в PEP 563) позволяет использовать обычные типы вместо прямых ссылок:
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING: # Only imports the below statements during type checking
from src.main import Foo
class SpecificException(Exception):
def __init__(self, cause: Foo): # Foo can be used in type hints without issue
self.cause = cause
def __str__(self):
return f'Expected 3 but got {self.cause.attrib}.'
В Python 3.10+ from __future__ import annotations
активен по умолчанию, поэтому его можно не указывать.
Этот ответ основан на еще одном решении, позволяющем выбраться из циклического импорта. дыра в Python Стефана Липпенса.
Хорошо, думаю, у меня есть довольно крутое решение. Допустим, у вас есть файл 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.
- person Matthias Fripp; 04.06.2015