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