Циклический импорт Python?

101

Итак, я получаю эту ошибку

Traceback (most recent call last):
  File "/Users/alex/dev/runswift/utils/sim2014/simulator.py", line 3, in <module>
    from world import World
  File "/Users/alex/dev/runswift/utils/sim2014/world.py", line 2, in <module>
    from entities.field import Field
  File "/Users/alex/dev/runswift/utils/sim2014/entities/field.py", line 2, in <module>
    from entities.goal import Goal
  File "/Users/alex/dev/runswift/utils/sim2014/entities/goal.py", line 2, in <module>
    from entities.post import Post
  File "/Users/alex/dev/runswift/utils/sim2014/entities/post.py", line 4, in <module>
    from physics import PostBody
  File "/Users/alex/dev/runswift/utils/sim2014/physics.py", line 21, in <module>
    from entities.post import Post
ImportError: cannot import name Post

и вы можете видеть, что я использую тот же оператор импорта дальше, и он работает? Есть ли какое-то неписаное правило о циклическом импорте? Как мне использовать тот же класс ниже по стеку вызовов?

CpILL
источник

Ответы:

162

Я думаю, что ответ jpmc26, хотя и не ошибочный , слишком сильно зависит от циклического импорта. Они могут нормально работать, если вы их правильно настроите.

Самый простой способ сделать это - использовать import my_moduleсинтаксис, а не from my_module import some_object. Первый будет работать почти всегда, даже если он my_moduleнас импортирует обратно. Последний работает, только если my_objectон уже определен вmy_module , чего может не быть при циклическом импорте.

Чтобы быть конкретным для вашего случая: попробуйте изменить действие entities/post.py, import physicsа затем ссылаться на него, physics.PostBodyа не просто PostBodyнапрямую. Точно так же измените действие physics.py, import entities.postа затем используйте, entities.post.Postа не просто Post.

Blckknght
источник
5
Совместим ли этот ответ с относительным импортом?
Джо
19
Почему так происходит?
Хуан Пабло Сантос
5
Неправильно говорить, что несинтаксис fromвсегда работает. Если у меня есть class A(object): pass; class C(b.B): passв модуле a и class B(a.A): passв модуле b, то циклический импорт все еще является проблемой, и это не сработает.
CrazyCasta
1
Вы правы, любые циклические зависимости в коде верхнего уровня модулей (например, базовые классы объявлений классов в вашем примере) будут проблемой. Это такая ситуация, когда ответ jpmc о том, что вам следует реорганизовать организацию модуля, вероятно, на 100% верен. Либо переместите класс Bв модуль a, либо переместите класс Cв модуль, bчтобы вы могли разорвать цикл. Также стоит отметить, что даже если только в одном направлении круга задействован код верхнего уровня (например, если класс Cне существует), вы можете получить ошибку, в зависимости от того, какой модуль был импортирован первым другим кодом.
Blckknght
2
@TylerCrompton: Я не уверен, что вы имеете в виду, говоря «импорт модуля должен быть абсолютным». Циклический относительный импорт может работать, пока вы импортируете модули, а не их содержимое (например from . import sibling_module, нет from .sibling_module import SomeClass). Есть еще одна тонкость, когда __init__.pyфайл пакета участвует в циклическом импорте, но проблема возникает редко и, вероятно, является ошибкой в importреализации. См. Ошибку Python 23447 , для которой я отправил патч (который, к сожалению , не работает).
Blckknght
51

Когда вы импортируете модуль (или его член) в первый раз, код внутри модуля выполняется последовательно, как и любой другой код; например, он не обрабатывается иначе, чем тело функции. importПросто команда , как и любого другого (присвоение, вызов функции, def, class). Предполагая, что ваш импорт происходит в верхней части скрипта, происходит следующее:

  • Когда вы пытаетесь импортировать Worldиз world, worldскрипт запускается.
  • В worldимпорте сценария Field, который вызывает entities.fieldсценарий , чтобы получить казнена.
  • Этот процесс продолжается, пока вы не дойдете до entities.postскрипта, потому что вы пытались импортироватьPost
  • entities.postСкрипт вызывает physicsмодуль будет выполняться , поскольку он пытается импортPostBody
  • Наконец, physicsпытается импортироватьPost изentities.post
  • Я не уверен, entities.postсуществует ли модуль в памяти, но это не имеет значения. Либо модуля нет в памяти, либо у модуля еще нет Postчлена, потому что он не завершил выполнение для определенияPost
  • В любом случае возникает ошибка, потому что Postнет файла для импорта

Так что нет, он не «работает дальше в стеке вызовов». Это трассировка стека того, где произошла ошибка, что означает, что при импорте Postв этот класс произошла ошибка . Вам не следует использовать циклический импорт. В лучшем случае это дает незначительную пользу (как правило, никакой пользы) и вызывает подобные проблемы. Это обременяет любого разработчика, обслуживающего его, заставляя ходить по яичной скорлупе, чтобы не сломать ее. Реорганизуйте организацию модуля.

jpmc26
источник
1
Должно быть isinstance(userData, Post). В любом случае у вас нет выбора. Циклический импорт работать не будет. Тот факт, что у вас есть циклический импорт, для меня является запахом кода. Он предполагает, что у вас есть некоторые функции, которые следует перенести в третий модуль. Я не мог сказать что, не глядя на оба класса целиком.
jpmc26 05
3
@CpILL Через некоторое время мне в голову пришла очень хитрая опция. Если вы пока не можете обойтись без этого (из-за нехватки времени или чего-то еще), вы можете выполнить импорт локально внутри метода, в котором вы его используете. Тело функции внутри defне выполняется до тех пор, пока функция не будет вызвана, поэтому импорт не произойдет, пока вы не вызовете функцию. К тому времени imports должен работать, поскольку один из модулей будет полностью импортирован до вызова. Это абсолютно отвратительный прием, и он не должен оставаться в вашей кодовой базе в течение длительного времени.
jpmc26 05
15
Я думаю, ваш ответ слишком сильно зависит от циклического импорта. Циркулярный импорт обычно работает, если вы делаете просто, import fooа не from foo import Bar. Это потому, что большинство модулей просто определяют вещи (например, функции и классы), которые запускаются позже. Модули, которые делают важные вещи при их импорте (например, скрипт, не защищенный if __name__ == "__main__"), могут по-прежнему вызывать проблемы, но это не так часто.
Blckknght 05
6
@Blckknght Я думаю, что вы настраиваете себя на то, чтобы тратить время на странные проблемы, которые другим людям придется исследовать и которые сбивают с толку, если вы используете циклический импорт. Они заставляют вас тратить время на то, чтобы не споткнуться о них, и вдобавок к этому запах кода требует рефакторинга вашего дизайна. Возможно, я ошибался относительно того, осуществимы ли они технически, но это ужасный дизайнерский выбор, который рано или поздно вызовет проблемы. Ясность и простота - святой Грааль в программировании, и в моей книге циклический импорт нарушает и то, и другое.
jpmc26
6
Альтернативно; вы слишком разделили свою функциональность, и это является причиной циклического импорта. Если у вас есть две вещи, которые все время полагаются друг на друга ; может быть лучше просто поместить их в один файл. Python - это не Java; нет причин не группировать функциональность / классы в один файл, чтобы предотвратить странную логику импорта. :-)
Марк Рибау
41

Чтобы понять циклические зависимости, вам нужно помнить, что Python - это, по сути, язык сценариев. Выполнение операторов вне методов происходит во время компиляции. Операторы импорта выполняются так же, как вызовы методов, и чтобы понять их, вы должны думать о них как о вызовах методов.

Что происходит при импорте, зависит от того, существует ли уже импортируемый файл в таблице модуля. Если это так, Python использует все, что в данный момент находится в таблице символов. Если нет, Python начинает читать файл модуля, компилируя / выполняя / импортируя все, что он там находит. Символы, на которые ссылаются во время компиляции, обнаруживаются или нет, в зависимости от того, были ли они видны компилятору или еще не увидены компилятором.

Представьте, что у вас есть два исходных файла:

Файл X.py

def X1:
    return "x1"

from Y import Y2

def X2:
    return "x2"

Файл Y.py

def Y1:
    return "y1"

from X import X1

def Y2:
    return "y2"

Теперь предположим, что вы компилируете файл X.py. Компилятор начинает с определения метода X1, а затем выполняет оператор импорта в X.py. Это заставляет компилятор приостановить компиляцию X.py и начать компиляцию Y.py. Вскоре после этого компилятор выполняет оператор импорта в Y.py. Поскольку X.py уже находится в таблице модулей, Python использует существующую неполную таблицу символов X.py для удовлетворения любых запрошенных ссылок. Любые символы, появляющиеся перед оператором импорта в X.py, теперь находятся в таблице символов, а символы после них - нет. Поскольку X1 теперь появляется перед оператором импорта, он успешно импортирован. Затем Python возобновляет компиляцию Y.py. При этом он определяет Y2 и завершает компиляцию Y.py. Затем он возобновляет компиляцию X.py и находит Y2 в таблице символов Y.py. В конечном итоге компиляция завершается без ошибки.

Что-то совсем другое произойдет, если вы попытаетесь скомпилировать Y.py из командной строки. При компиляции Y.py компилятор выполняет оператор импорта до того, как он определит Y2. Затем начинается компиляция X.py. Вскоре он попадает в оператор импорта в X.py, который требует Y2. Но Y2 не определен, поэтому компиляция не выполняется.

Обратите внимание, что если вы измените X.py для импорта Y1, компиляция всегда будет успешной, независимо от того, какой файл вы компилируете. Однако если вы измените файл Y.py для импорта символа X2, ни один файл не будет компилироваться.

Каждый раз, когда модуль X или любой модуль, импортированный X, может импортировать текущий модуль, НЕ используйте:

from X import Y

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

import X
z = X.Y

Предположим, что модуль X импортирует этот модуль до того, как этот модуль импортирует X. Далее предположим, что Y определен в X после оператора импорта. Тогда Y не будет определяться при импорте этого модуля, и вы получите ошибку компиляции. Если этот модуль сначала импортирует Y, вы можете обойтись без него. Но когда один из ваших коллег невинно меняет порядок определений в третьем модуле, код ломается.

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

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

#import X   (actual import moved down to avoid circular dependency)

В целом это плохая практика, но иногда ее трудно избежать.

Джин Олсон
источник
2
Я не думаю, что в python есть компилятор или время компиляции,
Джери Ван 08
6
Python делает есть компилятор, и будет составлена @pkqxdd, компиляция просто как правило , скрыта от пользователя. Это может немного сбивать с толку, но автору было бы сложно дать это превосходно ясное описание того, что происходит, без какой-либо ссылки на Python, несколько затененный, «время компиляции».
Хэнк
Я попробовал это на своей машине и получил другой результат. Запустил X.py, но получил ошибку «не могу импортировать имя 'Y2' из 'Y'». Однако побежал Y.py без проблем. Я использую Python 3.7.5, не могли бы вы объяснить, в чем проблема?
Сюэфэн Хуан,
18

Тем из вас, кто, как и я, пришел к этой проблеме из Django, вы должны знать, что в документации есть решение: https://docs.djangoproject.com/en/1.10/ref/models/fields/#foreignkey

"... Чтобы ссылаться на модели, определенные в другом приложении, вы можете явно указать модель с полной меткой приложения. Например, если указанная выше модель производителя определена в другом приложении под названием production, вам нужно будет использовать:

class Car(models.Model):
    manufacturer = models.ForeignKey(
        'production.Manufacturer',
        on_delete=models.CASCADE,
)

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

Малик А. Руми
источник
6
Я знаю, что не должен использовать комментарии, чтобы сказать «спасибо», но это мучило меня уже несколько часов. Спасибо Спасибо спасибо!!!
MikeyE
Я согласен с @MikeyE. Я прочитал несколько блогов и Stackoverflow, пытаясь исправить это с помощью PonyORM. Если другие говорят, что это плохая практика или почему вы должны кодировать свои классы так, чтобы они были круговыми, ну, ORM - это именно то место, где это происходит. Поскольку во многих примерах все модели помещаются в один файл, и мы следуем этим примерам, за исключением того, что мы используем модель для каждого файла, проблема не ясна, когда Python не удается скомпилировать. Тем не менее, ответ очень прост. Как сказал Майк, большое спасибо.
trash80
4

Мне удалось импортировать модуль внутри функции (только), для которой требуются объекты из этого модуля:

def my_func():
    import Foo
    foo_instance = Foo()
Александр Шуберт
источник
как элегантен питон
Яро
2

Если вы столкнетесь с этой проблемой в довольно сложном приложении, рефакторинг всего вашего импорта может оказаться обременительным. PyCharm предлагает быстрое исправление для этого, которое также автоматически изменит все использование импортированных символов.

введите описание изображения здесь

Андреас Бергстрём
источник
0

Я использовал следующее:

from module import Foo

foo_instance = Foo()

но чтобы избавиться от этого, circular referenceя сделал следующее, и это сработало:

import module.foo

foo_instance = foo.Foo()
MKJ
источник