Понимание поведения импорта 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 5

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 действительно не может заставить его работать. Правильный способ решить эту проблему-либо:

  1. Будьте осторожны, чтобы не вводить это в свой код
  2. Сделайте импорт внутренних функций, прямо в нужном месте

Что лично я предпочитаю последнее.

Почему модульная система в Python не будет отмечать успешно загруженный модуль, пока он не будет загружен. Так что на ваш "импорт", что Python не знаю, что это уже загружены "а" до тех пор, пока все зависимые нагрузки "В" и " с " не будут выполнены, как это прошло через весь "a.py-файл. Поэтому при обработке " импорта c "он снова попытается" импортировать a "вместо того, чтобы обнаружить, что" a " - это то, что он может пропустить.