Круговой (или циклический) импорт в Python
Что произойдет, если два модуля импортируют друг друга?
Чтобы обобщить проблему, как насчет циклического импорта в Python?
18 ответов
В прошлом году на 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
, Потому что теперь каждый модуль требует, чтобы другой модуль уже был импортирован (чтобы существующее имя импортировалось), прежде чем его можно будет импортировать.
Циклический импорт завершается, но вы должны быть осторожны, чтобы не использовать циклически импортированные модули во время инициализации модуля.
Рассмотрим следующие файлы:
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
,
Как и другие ответы описывают этот шаблон приемлем в 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)
Приложение B
# 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: Вы должны прочитать весь этот пост голосом Дэвида Бизли.
К моему удивлению, еще никто не упомянул циклический импорт, вызванный подсказками типов.
Если у вас есть циклический импорт только в результате хинтинга типов, их можно аккуратно избежать.
Подумайте, что использует исключения из другого файла:
from src.exceptions import SpecificException
class Foo:
def __init__(self, attrib: int):
self.attrib = attrib
raise SpecificException(Foo(5))
И выделенный класс исключения:
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
и наоборот через и
SpecificException
.
Потому что требуется только в
exceptions.py
во время проверки типов мы можем безопасно сделать его импорт условным, используя
TYPE_CHECKING
константа из модуля набора текста . Постоянная только
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.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
, поэтому он посетит модуль B - Первая строка в модуле b
import a
, поэтому он посетит модуль А - Первая строка в модуле А
import b
но обратите внимание, что эта строка больше не будет выполняться снова, потому что каждый файл в python выполняет строку импорта только один раз, не имеет значения, где и когда она выполняется. так что он перейдет к следующей строке и напечатает"This is from module a"
, - После завершения посещения всего модуля a из модуля b мы все еще находимся в модуле b. поэтому следующая строка напечатает
"This is from module b"
- Строки модуля b выполнены полностью. поэтому мы вернемся к модулю a, где мы начали модуль b.
- Строка import b уже выполнена и больше не будет выполняться. следующая строка напечатает
"This is from module a"
и программа будет закончена.
У меня есть пример, который поразил меня!
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
Здесь есть много отличных ответов. Хотя обычно есть быстрые решения проблемы, некоторые из которых кажутся более питоническими, чем другие, если у вас есть роскошь провести некоторый рефакторинг, другой подход - проанализировать организацию вашего кода и попытаться удалить циклическую зависимость. Вы можете обнаружить, например, что у вас есть:
Файл 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.
Я полностью согласен с ответом питона здесь. Но я наткнулся на некоторый код, который имел недостатки при циклическом импорте и вызывал проблемы при попытке добавить модульные тесты. Таким образом, чтобы быстро исправить это без изменения всего, вы можете решить проблему, выполнив динамический импорт.
# 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")
Опять же, это не постоянное исправление, но может помочь кому-то, кто хочет исправить ошибку импорта, не изменяя слишком много кода.
Ура!
Предположим, вы запускаете тестовый файл Python с именем
request.py
В request.py вы пишете
import request
так что это, скорее всего, циклический импорт.
Решение Просто измените свой тестовый файл на другое имя, например
aaa.py
, Кроме как
request.py
.
Круговой импорт может сбивать с толку, потому что импорт делает две вещи:
- он выполняет импортированный код модуля
- добавляет импортированный модуль в глобальную таблицу символов модуля
Первое выполняется только один раз, а второе - в каждом операторе импорта. Круговой импорт создает ситуацию, когда импортирующий модуль использует импортированный с частично выполненным кодом. В результате он не увидит объекты, созданные после оператора импорта. Ниже пример кода демонстрирует это.
Круговой импорт не является последним злом, которого следует избегать любой ценой. В некоторых средах, таких как 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 ()
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
Нам нравится использовать модули в Python, и почему бы и нет, они предоставляют дополнительные функции, улучшают практику кодирования и всегда создают правильную структуру при использовании. Но много раз неосознанно мы можем столкнуться с проблемами циклического импорта python, если у нас случайно будет другой файл, названный именем модуля. Поскольку python предпочитает сначала импортировать из локального текущего каталога, а затем из пакетов сайта, это создаст проблему циклического импорта.
Как правило, проблема кругового импорта Python возникает, когда вы случайно называете свой рабочий файл так же, как имя модуля, и эти модули зависят друг от друга. Таким образом, python открывает тот же файл, который вызывает циклический цикл и в конечном итоге выдает ошибку.
Например, когда вы называете свой файл как random.py и пытаетесь импортировать «из случайного импорта randint», он выдает циклическую ошибку импорта (также называемую частично инициализированным модулем).
Эта проблема возникла в моем случае, когда один из моих файлов Python имел то же имя, что и библиотека. Пример : у вас есть пакет abc и файл abc.py.import abc
выдаст циклическую ошибку.
Я тоже сталкиваюсь с этой ошибкой. В моем случае я исправил это, заменив операторы импорта в__init__.py
Моя ситуация
инициализировать .py
from .b import B, C
form .a import A
б.пи
from <module name of __init__.py> import A
а.py
class A():
a = 42
Мое решение
инициализировать .py
form .a import A
from .b import B, C
Хорошо, я думаю, у меня есть довольно крутое решение. Допустим, у вас есть файл 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."
Вуаля.
Это может быть другое решение, сработало для меня.
def MandrillEmailOrderSerializer():
from sastaticketpk.apps.flights.api.v1.serializers import MandrillEmailOrderSerializer
return MandrillEmailOrderSerializer
email_data = MandrillEmailOrderSerializer()(order.booking).data
bar.py
print('going to import foo')
from foo import printx
foo.py
print('trying to import bar')
import bar
def printx():
print('x')
$ python bar.py
going to import foo
trying to import bar
going to import foo
Traceback (most recent call last):
File "bar.py", line 2, in <module>
from foo import printx
File "/home/suhail/Desktop/temp/circularimport/foo.py", line 2, in <module>
import bar
File "/home/suhail/Desktop/temp/circularimport/bar.py", line 2, in <module>
from foo import printx
ImportError: cannot import name 'printx' from partially initialized module 'foo' (most likely due to a circular import) (/home/suhail/Desktop/temp/circularimport/foo.py)