Подтвердить что ты не робот

Почему Python `from` формы оператора импорта связывает имя модуля?

У меня есть проект Python со следующей структурой:

testapp/
├── __init__.py
├── api
│   ├── __init__.py
│   └── utils.py
└── utils.py

Все модули пустые, кроме testapp/api/__init__.py, который имеет следующий код:

from testapp import utils

print "a", utils

from testapp.api.utils import x

print "b", utils

и testapp/api/utils.py, который определяет x:

x = 1

Теперь из корня я импортирую testapp.api:

$ export PYTHONPATH=$PYTHONPATH:.
$ python -c "import testapp.api"
a <module 'testapp.utils' from 'testapp/utils.pyc'>
b <module 'testapp.api.utils' from 'testapp/api/utils.pyc'>

Результат импорта меня удивляет, потому что он показывает, что второй оператор import перезаписал utils. Однако документы указывают, что оператор не свяжет имя модуля:

Форма from не связывает имя модуля: она проходит через список идентификаторов, просматривает каждый из них в модуле, найденном на этапе (1) и связывает имя в локальном пространстве имен с объектом найдено.

И действительно, когда в терминале я использую оператор from ... import ..., имена модулей не вводятся:

>>> from os.path import abspath
>>> path
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'path' is not defined

Я подозреваю, что это связано с Python во время второго оператора импорта, пытающегося импортировать testapp.api.utils, который ссылается на testapp.utils и не работает, но я не уверен.

Что здесь происходит?

4b9b3361

Ответ 1

Из документации системы импорта:

Когда подмодуль загружается с использованием любого механизма (например, importlib API, операторы import или import-from или встроенные __import__()) привязка помещается в пространство имен родительских модулей в подмодуль объект. Например, если пакет spam имеет подмодуль foo, после import spam.foo, spam будет иметь атрибут foo, который связанных с подмодулем. Допустим, у вас есть следующий каталог Структура:

spam/
    __init__.py
    foo.py
    bar.py

и spam/__init__.py имеют в нем следующие строки:

from .foo import Foo
from .bar import Bar

тогда выполнение следующего значения привязывает имя к foo и bar в модуль spam:

>>> import spam
>>> spam.foo
<module 'spam.foo' from '/tmp/imports/spam/foo.py'>
>>> spam.bar
<module 'spam.bar' from '/tmp/imports/spam/bar.py'>

Учитывая знакомые правила привязки имен Pythons, это может показаться неожиданным, но на самом деле это фундаментальная особенность системы импорта. инвариантный холдинг состоит в том, что если у вас есть sys.modules['spam'] и sys.modules['spam.foo'] (как и после указанного импорта), последний должен отображаться как атрибут foo первого.

Если вы выполняете from testapp.api.utils import x, оператор импорта не загружает utils в локальное пространство имен. Тем не менее, импортная техника загрузит utils в пространство имен testapp.api, чтобы сделать дальнейшие импортные операции правильными. Просто случается, что в вашем случае testapp.api также является локальным пространством имен, поэтому вы получаете сюрприз.