Понимание поведения импорта Python и циклических зависимостей
Примечание : речь идет об импорте модулей , а не классов, функций из этих модулей, поэтому я не думаю, что это дубликат mane "ImportError: cannot import name" приводит к SO, по крайней мере, я не нашел тот, который соответствует этому.
Я понимаю, что импорт классов или функций из модулей по имени может вызвать проблему, так как сам модуль, возможно, еще не был полностью инициализирован, если есть циклическая зависимость, но это не так здесь.
Чтобы воспроизвести эту проблему, создайте три модуля, которые имеют циклическую зависимость от нее.
Сначала создайте пакет:
$ mkdir pkg
$ touch pkg/__init__.py
Затем создайте pkg/a.py, с содержанием:
from __future__ import print_function
from __future__ import absolute_import
from . import b
def A(x):
print('I am A, x={}.'.format(x))
b.B(x + 1)
def Z(x):
print('I am Z, x={}. I'm done now!'.format(x))
И pkg/b.py, с содержанием:
from __future__ import print_function
from __future__ import absolute_import
from . import c
def B(x):
print('I am B, x={}.'.format(x))
c.C(x * 2)
И pkg/c.py, с содержанием:
from __future__ import print_function
from __future__ import absolute_import
from . import a
def C(x):
print('I am C, x={}.'.format(x))
a.Z(x ** 2)
И a main.py (в верхнем каталоге), который вызывает в них:
from __future__ import print_function
from __future__ import absolute_import
from pkg import a
if __name__ == '__main__':
a.A(5)
Я ожидал, что не будет никаких проблем с циклической зависимостью, так как нет ссылок на элементы внутри каждого из модулей во время импорта (т. е. никаких ссылок на a. A из модулей b или c, за исключением вызова внутри тела c. C).
И, действительно, запуск этого с python3 работает просто отлично:
$ python3 main.py
I am A, x=5.
I am B, x=6.
I am C, x=12.
I am Z, x=144. I'm done now!
(это Python 3.5.3 на Debian Stretch, для записи.)
Но с python2 (Python 2.7.13) это действительно не работает, и он жалуется на циклическую зависимость...$ python main.py
Traceback (most recent call last):
File "main.py", line 5, in <module>
from pkg import a
File "/tmp/circular/pkg/a.py", line 5, in <module>
from . import b
File "/tmp/circular/pkg/b.py", line 5, in <module>
from . import c
File "/tmp/circular/pkg/c.py", line 5, in <module>
from . import a
ImportError: cannot import name a
Итак, мои вопросы таковы:
-
Почему я столкнулся с ... проблема циклической зависимости, если я не импортирую или ссылаюсь на определенные классы или функции из моих модулей, а только на сами модули?
-
Почему это происходит только на Python 2? (Ссылки на PEP, код, заметки о выпуске или статьи об исправлении этого в Python 3 будут оценены.)
-
Есть ли способ избежать этой проблемы в Python 2, все еще не нарушая циклическую зависимость модулей? Я считаю, что не все циклические зависимости вызывают эта проблема (даже в Python 2), поэтому мне интересно, какие случаи безопасны, а какие нет...
2 ответа:
Когда Python начинает загрузку модуля
pkg.a
, он устанавливаетsys.modules['pkg.a']
в соответствующий объект модуля, но он только устанавливает атрибутa
объекта модуляpkg
в самом конце загрузки модуляpkg.a
. Это будет актуально позже.
Относительно импорта
from
импортирует, и они ведут себя так же. После того, какfrom . import whatever
выяснит, что.
относится к пакетуpkg
, он переходит к обычной логикеfrom pkg import whatever
.Когда
Вы знаете, как можно делать такие вещи, какc.py
попадает вfrom . import a
, сначала он видит, чтоpkg.a
является уже вsys.modules
, указывая, чтоpkg.a
уже загружен или находится в середине загрузки. (Он находится в середине загрузки, но этот путь кода не волнует.) Он переходит ко второй части своей работы, извлекаяpkg.a
и присваивая его имениa
в локальном пространстве имен, но он не просто извлекаетsys.modules['pkg.a']
для этого.from os import open
, даже еслиos.open
- это функция, а не модуль? Такого рода импорт не может пройти черезsys.modules['os.open']
, потому чтоos.open
не является модулем и не находится вsys.modules
. Вместо этого всеfrom
импорта, включая все относительные импорта, пытаются найти атрибут в модуле, из которого они импортируют имена.from . import a
ищет атрибутa
в объекте модуляpkg
, но его там нет, потому что этот атрибут устанавливается только после завершения загрузкиpkg.a
.На Python 2, Вот и все. Конец импорта.
ImportError
здесь. На Python 3 (в частности, 3.5+), потому что они хотели стимулировать относительный импорт и это поведение действительно неудобно,from
импорт попробуйте еще один шаг. Если поиск атрибута не удается, теперь они пытаютсяsys.modules
.pkg.a
находится вsys.modules
, поэтому импорт выполняется успешно. Вы можете увидеть обсуждение этого изменения в CPython issue tracker по адресуissue 17636 .
Я не уверен, как Python 3 решил эту проблему, но мой опыт говорит, что Python 2 действительно не может заставить его работать. Правильный способ решить эту проблему-либо:
- Будьте осторожны, чтобы не вводить это в свой код
- Сделайте импорт внутренних функций, прямо в нужном месте
Что лично я предпочитаю последнее.
Почему модульная система в Python не будет отмечать успешно загруженный модуль, пока он не будет загружен. Так что на ваш "импорт", что Python не знаю, что это уже загружены "а" до тех пор, пока все зависимые нагрузки "В" и " с " не будут выполнены, как это прошло через весь "a.py-файл. Поэтому при обработке " импорта c "он снова попытается" импортировать a "вместо того, чтобы обнаружить, что" a " - это то, что он может пропустить.