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

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

9 ответов

Решение

Я думаю, что в настоящее время принятый ответ от 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 post а затем использовать post.Post а не просто Post,

Когда вы импортируете модуль (или его член) в первый раз, код внутри модуля выполняется последовательно, как и любой другой код; например, это не трактуется иначе, чем тело функции. 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 в этом классе. Вы не должны использовать круговой импорт. В лучшем случае, он имеет незначительную выгоду (как правило, никакой выгоды), и это вызывает такие проблемы. Это обременяет любого разработчика, заставляя его ходить по скорлупе, чтобы не сломать его. Рефакторинг вашего модуля организации.

Чтобы понимать циклические зависимости, вы должны помнить, что 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 после оператора import. Тогда Y не будет определен при импорте этого модуля, и вы получите ошибку компиляции. Если этот модуль импортирует Y первым, вы можете обойтись без него. Но когда один из ваших коллег невинно изменит порядок определений в третьем модуле, код сломается.

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

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

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

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

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

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

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

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

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

def my_func():
    import Foo
    foo_instance = Foo()

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

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

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

from module import Foo

foo_instance = Foo()

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

import module.foo

foo_instance = foo.Foo()

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

      def get_simple_obj():
    from another import Simple
    return Simple

class Example(get_simple_obj()):
    pass

class NotCircularImportError:
    pass

В этой ситуации, another.pyМодуль может легко импортировать NotCircularImportError без каких-либо проблем.

просто проверьте имя файла, чтобы убедиться, что оно не совпадает с импортируемой библиотекой.

Например - sympy.py

      import sympy as sym
Другие вопросы по тегам