7

Как решить проблему "ImportError: Невозможно импортировать имя X" или "AttributeError: ... (вероятно, из-за циклического импорта)"?

1

У меня есть код, распределенный по нескольким файлам, которые пытаются импортировать друг друга следующим образом:

main.py:

from entity import Ent

entity.py:

from physics import Physics
class Ent:
    ...

physics.py:

from entity import Ent
class Physics:
    ...

Когда я запускаю main.py, я получаю следующую ошибку:

Traceback (most recent call last):
  File "main.py", line 2, in <module>
    from entity import Ent
  File ".../entity.py", line 5, in <module>
    from physics import Physics
  File ".../physics.py", line 2, in <module>
    from entity import Ent
ImportError: cannot import name Ent

Я предполагаю, что ошибка связана с тем, что я дважды импортирую entity — один раз в main.py, а затем снова в physics.py. Как я могу обойти эту проблему?

5 ответ(ов)

6

У вас возникают проблемы с круговыми зависимыми импортами. Модуль physics.py импортируется из модуля entity до того, как класс Ent будет определен, а в модуле physics происходит попытка импортировать entity, который уже находится в процессе инициализации. Чтобы решить эту проблему, необходимо удалить зависимость от модуля physics в модуле entity.

1

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

Например:

import SomeModule

def someFunction(arg):
    from some.dependency import DependentClass

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

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

1

Это круговая зависимость, которую можно решить без каких-либо структурных изменений в коде. Проблема возникает из-за того, что в vector вы требуете, чтобы entity был доступен для использования сразу, и наоборот. Причина этой проблемы заключается в том, что вы пытаетесь получить доступ к содержимому модуля до его готовности, используя конструкцию from x import y. На самом деле это почти то же самое, что и:

import x
y = x.y
del x

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

a = module()  # импортируем a

# остальная часть модуля

a.update_contents(real_a)

Для того чтобы Python мог работать с круговыми зависимостями, вы должны использовать стиль import x:

import x
class cls:
    def __init__(self):
        self.y = x.y

Поскольку вы больше не ссылаетесь на содержимое модуля на верхнем уровне, Python может скомпилировать модуль, не обращая внимания на содержимое круговой зависимости. Под верхним уровнем я имею в виду строки, которые будут выполняться во время компиляции, в отличие от содержимого функций (например, y = x.y). Статические или классные переменные, обращающиеся к содержимому модуля, также будут вызывать проблемы.

0

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

Я перезапустил ядро Jupyter, и ошибка исчезла.

0

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

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

Вот пример:

a.py

from test.b import b2

def a1():
    print('a1')
    b2()

b.py

from test.a import a1

def b1():
    print('b1')
    a1()

def b2():
    print('b2')

if __name__ == '__main__':
    b1()

В этом случае вы получите ошибку импорта: ImportError: cannot import name 'a1'.

Но если переместить строку from test.b import b2 в a.py немного ниже, как показано ниже:

a.py

def a1():
    print('a1')
    b2()

from test.b import b2

Теперь код будет работать так, как задумано:

b1
a1
b2

Таким образом, перестановка импорта позволяет избежать циклической зависимости, и код выполняется корректно.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь