Подтвердить что ты не робот

Круговой импорт Python?

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

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

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

4b9b3361

Ответ 1

Я думаю, что ответ от 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.

Ответ 2

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

  • При попытке импортировать World из World выполняется выполнение World script.
  • World script импортирует Field, что вызывает выполнение entities.field script.
  • Этот процесс продолжается до тех пор, пока вы не достигнете entities.post script, потому что вы попытались импортировать Post
  • entities.post script вызывает выполнение модуля 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. Вскоре после этого компилятор попадает в оператор импорта в 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 компилятор попадает в оператор импорта до того, как он определит Y2. Затем он начинает компиляцию X.py. Вскоре он попадает в оператор import в X.py, для которого требуется Y2. Но Y2 - undefined, поэтому компиляция терпит неудачу.

Обратите внимание, что если вы измените 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)

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

Ответ 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,
)

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

Ответ 5

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

enter image description here

Ответ 6

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

from module import Foo

foo_instance = Foo()

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

import module.foo

foo_instance = foo.Foo()