Круговой (или циклический) импорт в 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 строки, в то время как должен был вывести бесконечность из-за циклического импорта. То, что происходит построчно при запуске "Модуля А", указано здесь:

  1. Первая строка import b, поэтому он посетит модуль B
  2. Первая строка в модуле b import a, поэтому он посетит модуль А
  3. Первая строка в модуле А import b но обратите внимание, что эта строка больше не будет выполняться снова, потому что каждый файл в python выполняет строку импорта только один раз, не имеет значения, где и когда она выполняется. так что он перейдет к следующей строке и напечатает "This is from module a",
  4. После завершения посещения всего модуля a из модуля b мы все еще находимся в модуле b. поэтому следующая строка напечатает "This is from module b"
  5. Строки модуля b выполнены полностью. поэтому мы вернемся к модулю a, где мы начали модуль b.
  6. Строка 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.

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

  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"

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)

Другие вопросы по тегам