Циклический (или циклический) импорт в Python

Что будет, если два модуля импортируют друг друга?

Чтобы обобщить проблему, как насчет циклического импорта в Python?


person Xolve    schedule 13.04.2009    source источник
comment
См. Также stackoverflow.com/questions/158268/   -  person Constantin    schedule 14.04.2009
comment
Также в качестве справки кажется, что циклический импорт разрешен на python 3.5 (и, вероятно, выше), но не на 3.4 (и, вероятно, ниже).   -  person Charlie Parker    schedule 08.02.2017
comment
Я использую python 3.7.2, и у меня все еще возникает ошибка времени выполнения из-за циклических зависимостей.   -  person Richard Whitehead    schedule 15.03.2019


Ответы (13)


Это было действительно хорошее обсуждение на comp.lang.python в прошлом году. Он довольно подробно отвечает на ваш вопрос.

Импорт на самом деле довольно прост. Просто запомните следующее:

'import' и 'from xxx import yyy' являются исполняемыми операторами. Они выполняются, когда запущенная программа достигает этой строки.

Если модуля нет в sys.modules, то при импорте создается новая запись модуля в sys.modules, а затем выполняется код в модуле. Он не возвращает управление вызывающему модулю до завершения выполнения.

Если модуль действительно существует в sys.modules, тогда импорт просто возвращает этот модуль независимо от того, завершил он выполнение или нет. По этой причине циклический импорт может возвращать модули, которые кажутся частично пустыми.

Наконец, исполняемый скрипт запускается в модуле с именем __main__, импорт скрипта под его собственным именем создаст новый модуль, не связанный с __main__.

Возьмите все это вместе, и вы не получите никаких сюрпризов при импорте модулей.

person Shane C. Mason    schedule 13.04.2009
comment
@meawoppl Не могли бы вы расширить этот комментарий, пожалуйста? Насколько конкретно они изменились? - person Dan Schien; 07.04.2016
comment
На данный момент единственная ссылка на циклический импорт в python3. Что нового? pages - это в версии 3.5. В нем говорится, что теперь поддерживается циклический импорт, связанный с относительным импортом. @meawoppl Вы нашли что-нибудь еще, чего нет на этих страницах? - person zezollo; 21.04.2016
comment
Они деф. не поддерживается в версиях 3.0–3.4. Или, по крайней мере, семантика успеха другая. Вот синопсис, который я нашел, в котором не упоминаются изменения 3.5. gist.github.com/datagrok/40bf84d5870c41a77dc6 - person meawoppl; 22.04.2016
comment
Не могли бы вы расширить это. Наконец, выполняющийся сценарий выполняется в модуле с именем main, импорт сценария под его собственным именем создаст новый модуль, не связанный с main. Итак скажем, что это файл a.py, и когда он работает как основная точка входа, теперь он будет основным, если в нем есть код, например, из импорта некоторой переменной. Тогда будет ли загружен тот же файл a.py в таблицу модулей sys? Значит ли это, что если в нем есть оператор print, он будет выполняться дважды? Один раз для основного файла и еще раз при импорте? - person variable; 30.08.2019
comment
Этому ответу 10 лет, и я хотел бы модернизировать обновление, чтобы убедиться, что он остается правильным в различных версиях Python, 2.x или 3.x. - person Fallenreaper; 30.10.2019

Если вы сделаете import foo (внутри bar.py) и import bar (внутри foo.py), он будет работать нормально. К тому времени, когда что-либо действительно запустится, оба модуля будут полностью загружены и будут иметь ссылки друг на друга.

Проблема в том, что вместо этого вы делаете from foo import abc (внутри bar.py) и from bar import xyz (внутри foo.py). Потому что теперь каждый модуль требует, чтобы другой модуль уже был импортирован (чтобы имя, которое мы импортируем, существовало), прежде чем он может быть импортирован.

person Community    schedule 14.04.2009
comment
Похоже, from foo import * и from bar import * тоже будут работать нормально. - person Akavall; 12.05.2014
comment
Проверьте правку сообщения выше, используя a.py/b.py. Он не использует from x import y, но по-прежнему получает ошибку циклического импорта - person Greg Ennis; 30.06.2014
comment
Это не совсем правда. Как и import * from, если вы попытаетесь получить доступ к элементу в циклическом импорте на верхнем уровне, поэтому до того, как скрипт завершит свой запуск, у вас будет такая же проблема. Например, если вы устанавливаете глобальный пакет в одном пакете из другого, и оба они включают друг друга. Я делал это, чтобы создать неаккуратную фабрику для объекта в базовом классе, где этот объект мог бы быть одним из множества подклассов, и используемый код не должен был знать, что он на самом деле создает. - person AaronM; 13.04.2016
comment
@ Акавалл Не совсем. При этом будут импортированы только те имена, которые доступны при выполнении оператора import. Таким образом, это не приведет к ошибке, но вы не сможете получить все ожидаемые переменные. - person augurar; 24.12.2016
comment
Обратите внимание: если вы выполняете from foo import * и from bar import *, все, что выполняется в foo, находится в фазе инициализации bar, а фактические функции в bar еще не определены ... - person Martian2049; 18.01.2017
comment
@pythoneer Интересно. Я абсолютно уверен, что импорт определенных функций из модуля был решением, в отличие от импорта всего модуля. Казалось бы, единственная независимая функция избавляет от проблемы, не так ли? - person Guimoute; 12.03.2020
comment
@ Martian2049 Что означает вторая часть ответа? - Проблема ... - Я не понял. Пожалуйста помоги. - person ; 08.06.2020
comment
Потрясающая технология! По очевидным причинам, зачем создавать модульную систему, которая обнаруживает все имена модулей до разрешения импорта? Бог руководил дизайном Python, это действительно впечатляюще. - person douche_satan; 20.02.2021

Циклический импорт завершается, но вы должны быть осторожны, чтобы не использовать циклически импортированные модули во время инициализации модуля.

Рассмотрим следующие файлы:

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.

person Torsten Marek    schedule 13.04.2009
comment
это во многом объясняет проблему, но как насчет решения? как мы могли правильно импортировать и распечатать x? другое решение выше не сработало для меня - person mehmet; 27.03.2018
comment
Я думаю, что этот ответ принесет большую пользу, если вы использовали __name__ вместо 'a'. Вначале я совершенно не понимал, почему файл запускается дважды. - person Bergi; 18.02.2020
comment
@mehmet Реорганизуйте свой проект, чтобы операторы импорта образовывали древовидную структуру (основной скрипт импортирует вспомогательные модули, которые сами могут импортировать свои вспомогательные модули и т. д.). Это обычно рекомендуемый подход. - person Jeyekomon; 06.01.2021

Как описывают другие ответы, этот шаблон приемлем в 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 поднимается, потому что он уже импортирован, он извлечет его из importcache.

PS: Вы должны прочитать весь этот пост голосом Дэвида Бизли.

person Sebastian Wozny    schedule 05.11.2015
comment
ImportError не возникает, если модуль уже импортирован. Модули можно импортировать столько раз, сколько вы хотите, т. Е. Импортировать; импортировать; в порядке. - person Yuras; 05.05.2016
comment
это сделало бы его 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
person Xolve    schedule 14.04.2009
comment
Как ты это исправил? Я пытаюсь понять циклический импорт, чтобы исправить мою собственную проблему, которая выглядит очень похожей на то, что вы делаете ... - person c089; 09.08.2010
comment
Эээ ... Думаю, я решил свою проблему с помощью этого невероятно уродливого хака. {{{если не 'foo.bar' в sys.modules: from foo import bar else: bar = sys.modules ['foo.bar']}}} Лично я считаю, что циклический импорт - ОГРОМНЫЙ предупреждающий знак о плохом коде дизайн... - person c089; 09.08.2010
comment
@ c089, или вы могли бы просто переместить import bar в foo.py в конец - person warvariuc; 05.08.2013
comment
Если bar и foo оба должны использовать gX, «самым чистым» решением будет поместить gX в другой модуль и заставить оба foo и bar импортировать этот модуль. (самый чистый в том смысле, что нет скрытых семантических зависимостей.) - person Tim Wilder; 18.12.2013
comment
У Тима есть хорошая точка зрения. В основном это потому, что 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 строки, в то время как должен был выводить бесконечность из-за циклического импорта. Что происходит построчно при запуске «Модуля А», перечислено здесь:

  1. Первая строка - import b. поэтому он посетит модуль b
  2. Первая строка в модуле b - import a. поэтому он посетит модуль a
  3. Первая строка в модуле a - 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", и программа будет завершена.
person Mohsen Haddadi    schedule 06.07.2018

Здесь есть много отличных ответов. Хотя обычно есть быстрые решения проблемы, некоторые из которых кажутся более питоническими, чем другие, если у вас есть роскошь провести некоторый рефакторинг, другой подход - проанализировать организацию вашего кода и попытаться удалить циклическую зависимость. Вы можете обнаружить, например, что у вас есть:

Файл 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.

person hlongmore    schedule 05.09.2019

Я полностью согласен с ответом 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")

Опять же, это не постоянное исправление, но может помочь кому-то, кто хочет исправить ошибку импорта, не меняя слишком много кода.

Ваше здоровье!

person radtek    schedule 02.03.2017

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

  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
person Jacek Błocki    schedule 30.06.2018

Предположим, вы запускаете тестовый файл Python с именем request.py. В request.py вы пишете

import request

так что это, скорее всего, циклический импорт.

Решение:

Просто измените свой тестовый файл на другое имя, например aaa.py, отличное от request.py.

Не используйте имена, которые уже используются другими библиотеками.

person Vincent    schedule 10.11.2020
comment
Это мой лучший ответ, поскольку моя проблема заключалась в том, что я назвал файл, похожий на имя библиотеки, из которой я импортирую. - person Akkad; 14.02.2021

Я решил проблему следующим образом, и он работает без ошибок. Рассмотрим два файла 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
person iudeen    schedule 09.10.2018

К моему удивлению, никто еще не упомянул циклический импорт, вызванный подсказками типов.
Если у вас есть циклический импорт только в результате подсказок типов, их можно аккуратно избежать.

Рассмотрим 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 Стефана Липпенса.

person Iterniam    schedule 24.05.2021

Хорошо, думаю, у меня есть довольно крутое решение. Допустим, у вас есть файл 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

Файл b:

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."

Вуаля.

person Cary Shindell    schedule 06.02.2015
comment
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