Почему циклический импорт, по-видимому, работает дальше в стеке вызовов, но затем повышает значение ImportError еще ниже?
Я получаю эту ошибку
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
и вы можете видеть, что я использую тот же оператор import выше, и это работает. Есть ли какое-то неписаное правило о циклическом импорте? Как мне использовать тот же класс дальше в стеке вызовов?
Смотрите также Что происходит при использовании взаимного или циклического (cyclic) импорта в Python? для получения общего обзора того, что разрешено и что вызывает проблему при циклическом импорте. Смотрите Что я могу сделать с "ImportError: не удается импортировать имя X" или "AttributeError: ... (скорее всего, из-за циклического импорта)"? о методах разрешения циклических зависимостей и их избежания.
Переведено автоматически
Ответ 1
Я думаю, что ответ jpmc26, хотя ни в коем случае не неправильный, слишком сильно зависит от циклического импорта. Они могут работать просто отлично, если вы правильно их настроили.
Самый простой способ сделать это - использовать import my_module
синтаксис, а не from my_module import some_object
. Первый почти всегда будет работать, даже если my_module
included импортирует нас обратно. Последнее работает, только если my_object
уже определено в my_module
, чего при циклическом импорте может и не быть.
Чтобы быть конкретным для вашего случая: попробуйте изменить entities/post.py
на do import physics
, а затем ссылаться на physics.PostBody
, а не просто PostBody
напрямую. Аналогично, измените physics.py
на do import entities.post
, а затем используйте entities.post.Post
, а не просто Post
.
Ответ 2
Когда вы импортируете модуль (или его элемент) в первый раз, код внутри модуля выполняется последовательно, как любой другой код; например, он обрабатывается так же, как тело функции. 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
в этом классе. Вам не следует использовать циклический импорт. В лучшем случае это дает незначительную выгоду (как правило, никакой пользы) и вызывает проблемы, подобные этой. Это обременяет любого разработчика, поддерживающего его, заставляя их ходить по яичной скорлупе, чтобы не разбить его. Реорганизуйте организацию вашего модуля.
Ответ 3
Чтобы понять циклические зависимости, вам нужно помнить, что 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, а затем выполняет команду import в X.py. Это заставляет компилятор приостановить компиляцию из X.py и начать компиляцию Y.py. Вскоре после этого компилятор запускает оператор import в Y.py. Поскольку X.py уже находится в таблице модулей, Python использует существующую таблицу неполных символов X.py для удовлетворения любых запрошенных ссылок. Все символы, появляющиеся перед инструкцией import в X.py, теперь находятся в таблице символов, а любые символы после - нет. Поскольку X1 теперь появляется перед инструкцией import, он успешно импортирован. Затем Python возобновляет компиляцию Y.py. При этом он определяет Y2 и завершает компиляцию Y.py. Затем он возобновляет компиляцию из X.py и находит Y2 в таблице символов Y.py. Компиляция в конечном итоге завершается без ошибки.
Что-то совсем другое происходит, если вы пытаетесь скомпилировать Y.py из командной строки. Во время компиляции Y.py компилятор обращается к инструкции import перед определением Y2 . Затем он начинает компиляцию X.py. Вскоре он попадает в инструкцию import в X.py для этого требуется Y2. Но Y2 не определен, поэтому компиляция завершается с ошибкой.
Пожалуйста, обратите внимание, что если вы измените X.py на импорт Y1, компиляция всегда будет успешной, независимо от того, какой файл вы скомпилируете. Однако, если вы измените file Y.py на импорт символа X2, ни один файл не будет скомпилирован.
Каждый раз, когда модуль X или любой модуль, импортированный X, может импортировать текущий модуль, не используйте:
from X import Y
Каждый раз, когда вы думаете, что может быть циклический импорт, вам также следует избегать ссылок во время компиляции на переменные в других модулях. Рассмотрите невинно выглядящий код.:
import X
z = X.Y
Предположим, что модуль X импортирует этот модуль до того, как этот модуль импортирует X. Далее предположим, что Y определено в X после инструкции import . Тогда Y не будет определен при импорте этого модуля, и вы получите ошибку компиляции. Если этот модуль импортирует Y первым, вам это сойдет с рук. Но когда один из ваших коллег невинно изменяет порядок определений в третьем модуле, код ломается.
В некоторых случаях вы можете разрешить циклические зависимости, переместив инструкцию import ниже определений символов, необходимых другим модулям. В приведенных выше примерах определения перед инструкцией import никогда не завершаются ошибкой. Определения после инструкции import иногда завершаются ошибкой, в зависимости от порядка компиляции. Вы даже можете поместить инструкции import в конец файла, если ни один из импортированных символов не требуется во время компиляции.
Обратите внимание, что перемещение операторов импорта вниз в модуле скрывает то, что вы делаете. Компенсируйте это комментарием в верхней части вашего модуля, примерно следующим:
#import X (actual import moved down to avoid circular dependency)
В целом это плохая практика, но иногда ее трудно избежать.
Ответ 4
Для тех из вас, кто, как и я, пришел к этой проблеме из 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,
)
Такого рода ссылки могут быть полезны при разрешении зависимостей циклического импорта между двумя приложениями. ..."