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


354

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

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



1
также просто для справки кажется, что круговой импорт разрешен на python 3.5 (и, возможно, за его пределами), но не 3.4 (и, вероятно, ниже).
Чарли Паркер

4
Я использую Python 3.7.2 и все еще имею ошибку времени выполнения из-за циклических зависимостей.
Ричард Уайтхед

Ответы:


282

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

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

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

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

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

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

Возьмите это вместе, и вы не должны удивляться при импорте модулей.


13
@meawoppl Не могли бы вы расширить этот комментарий, пожалуйста? Как конкретно они изменились?
Дэн Шиен

3
На данный момент единственная ссылка на циклический импорт в python3 "Что нового?" страниц в 3.5 . В нем говорится «Круговой импорт, связанный с относительным импортом, теперь поддерживается». @meawoppl Вы нашли что-нибудь еще, что не указано на этих страницах?
Зезолло

4
Они деф. не поддерживается в 3.0-3.4. Или, по крайней мере, семантика успеха различна. Вот краткий обзор, в котором я не упомянул 3,5 изменения. gist.github.com/datagrok/40bf84d5870c41a77dc6
meawoppl

Не могли бы вы рассказать подробнее об этом: «Наконец, исполняемый скрипт запускается в модуле с именем main , при импорте скрипта под собственным именем будет создан новый модуль, не связанный с main ». Итак, допустим, что файл является a.py, и когда он запускается как основная точка входа, теперь он является основным, если в нем есть код, подобный импорту некоторой переменной. Тогда будет ли тот же файл 'a.py' загружаться в таблицу модулей sys? Так значит ли это, что если он имеет оператор print, то он будет выполняться дважды? Один раз для основного файла и еще раз, когда импорт встречается?
переменная

Этому ответу 10 лет, и я хотел бы модернизированное обновление, чтобы гарантировать, что оно остается правильным в различных версиях Python, 2.x или 3.x
Fallenreaper

296

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

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


27
Кажется, что from foo import *и from bar import *будет работать нормально.
Акавалл,

1
Проверьте редактирование поста выше, используя a.py/b.py. Он не использует from x import y, и все же получает круговую ошибку импорта
Грег Эннис

2
Это не совсем правда. Точно так же, как import * from, если вы попытаетесь получить доступ к элементу циклического импорта на верхнем уровне, то есть до того, как скрипт завершит свой запуск, у вас возникнет та же проблема. Например, если вы устанавливаете глобальный пакет в одном пакете из другого, и они оба включают друг друга. Я делал это, чтобы создать неаккуратную фабрику для объекта в базовом классе, где этот объект мог бы быть одним из множества подклассов, и при использовании кода не нужно было знать, что он фактически создает.
AaronM

3
@Akavall Не совсем. Это будет импортировать только имена, которые доступны при выполнении importинструкции. Таким образом, он не выдаст ошибку, но вы можете не получить все ожидаемые переменные.
Авгурар

3
Обратите внимание, если вы делаете from foo import *и from bar import *, все выполнено в fooнаходится в фазе инициализации bar, а фактические функции до barсих пор не определена ...
Martian2049

100

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

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

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.


14
это сильно объясняет проблему, но как насчет решения? как мы могли бы правильно импортировать и распечатать x? другое решение выше не сработало для меня
mehmet

Я думаю, что этот ответ принесет большую пользу, если вы используете __name__вместо 'a'. Вначале я был совершенно сбит с толку, почему файл будет выполнен дважды.
Берги

30

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


9
Ошибка ImportError не вызывается, если модуль уже был импортирован. Модули можно импортировать столько раз, сколько вы хотите, т.е. «import a; import a;» в порядке.
Юрас

9

У меня есть пример, который поразил меня!

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

2
Как вы это исправили? Я пытаюсь понять циклический импорт, чтобы решить мою собственную проблему, которая очень похожа на то, что вы делаете ...
c089

12
Хм ... я думаю, что решил эту проблему с этим невероятно уродливым хаком. {{{если не 'foo.bar' в sys.modules: из панели импорта foo else: bar = sys.modules ['foo.bar']}}} Лично я считаю, что циклический импорт - ОГРОМНЫЙ предупреждающий знак о плохом коде дизайн ...
c089

5
@ c089, или вы могли бы просто двигаться import barв foo.pyдо конца
warvariuc

5
Если barи то, и fooдругое нужно использовать gX, самое «чистое» решение - это вставить gXдругой модуль, иметь оба fooи barимпортировать этот модуль. (самый чистый в том смысле, что нет никаких скрытых семантических зависимостей.)
Тим Уайлдер

2
Тим имеет хорошую точку зрения. В основном это потому, что barне могу найти gXв foo. круговой импорт хорош сам по себе, но он gXне определен при импорте.
Martian2049

9

Модуль 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. Первая строка в модуле 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"и программа будет завершена.

4

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

# 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")

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

Ура!


3

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

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


2

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

  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

1

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

0

Хорошо, я думаю, у меня есть довольно крутое решение. Допустим, у вас есть файл 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.
Матиас Фрипп
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.