Python import модуль разделяет имя с функцией в __init__.py - PullRequest
0 голосов
/ 08 января 2019

Мое дерево выглядит как

parent/
|--__init__.py
\--a.py

А содержание __init__.py составляет

import parent.a as _a
a = 'some string'

Когда я открываю Python на верхнем уровне и import parent.a, я получаю строку вместо модуля. Например import parent.a as the_a; type(the_a) == str.

Так что я думаю, что ОК, вероятно import импортирует имя из пространства имен parent, и теперь оно переопределено. Итак, я думаю, что могу пойти import parent._a as a_module. Но это не работает, так как «Нет модуля с именем _a».

Это очень запутанно. Функция может переопределить модуль с тем же именем, но модуль не может получить новое имя и выполнить «реэкспорт».

Есть какое-нибудь объяснение, о котором я не знаю? Или это документированная особенность?

Еще более запутанно, если я уберу оператор import в __init__.py, все снова вернется в норму (import parent.a; type(parent.a) is module). Но почему это отличается? Имя a в пространстве имен parent по-прежнему является строкой.

(я работал на Python 3.5.3 и 2.7.13 с одинаковыми результатами)

1 Ответ

0 голосов
/ 08 января 2019

В операторе import ссылка на модуль никогда не использует поиск атрибутов. Заявления

import parent.a  # as ...

и

from parent.a import ...  # as ...

всегда будет искать parent.a в пространстве имен sys.modules, прежде чем пытаться продолжить загрузку модуля с диска.

Однако для операторов from ... import name Python просматривает атрибуты разрешенного модуля, чтобы найти name, прежде чем искать подмодули.

Глобальные значения модуля и атрибуты объекта модуля - это одно и то же. При импорте Python добавляет подмодули как атрибуты (например, глобальные) в родительский модуль, но вы можете перезаписывать эти атрибуты, как вы это делали в своем коде. Однако при использовании импорта с путем к модулю parent.a атрибуты не вступают в игру.

Из раздела Подмодули справочной документации по системе импорта Python :

Когда подмодуль загружается с использованием любого механизма [...], в пространство имен родительского модуля помещается привязка к объекту подмодуля. Например, если пакет spam имеет подмодуль foo, после импорта spam.foo, spam будет иметь атрибут foo, который связан с подмодулем.

Ваш оператор import parent.a as _a добавляет два имени в пространство имен parent; сначала добавляется a, указывая на подмодуль parent.a, а затем также устанавливается _a, указывая на тот же объект.

Следующая строка заменяет имя a привязкой к объекту 'some string'.

Раздел Поиск того же подробно описывает, как Python выполняет поиск модуля при импорте:

Чтобы начать поиск, Python требуется полное имя импортируемого модуля [...].

[...]

Это имя будет использоваться на разных этапах поиска импорта, и это может быть пунктирный путь к подмодулю, например, foo.bar.baz. В этом случае Python сначала пытается импортировать foo, затем foo.bar и, наконец, foo.bar.baz. Если какой-либо из промежуточных импортеров завершается неудачно, ModuleNotFoundError повышается.

затем далее

Первое место, проверенное при поиске импорта, - sys.modules. Это отображение служит кешем всех модулей, которые были ранее импортированы, включая промежуточные пути. Таким образом, если foo.bar.baz был ранее импортирован, sys.modules будет содержать записи для foo, foo.bar и foo.bar.baz. Каждый ключ будет иметь в качестве значения соответствующий объект модуля.

Во время импорта имя модуля ищется в sys.modules, и, если оно присутствует, связанным значением является модуль, удовлетворяющий импорту, и процесс завершается. [...] Если имя модуля отсутствует, Python продолжит поиск модуля.

Поэтому при попытке импортировать parent.a все, что имеет значение, это то, что sys.modules['parent.a'] существует. sys.modules['parent'].a не справляются.

Только from module import ... когда-либо будет смотреть на атрибуты. Из import справочной документации :

Форма from использует чуть более сложный процесс:

  1. найти модуль, указанный в предложении from, загрузить и при необходимости инициализировать его;
  2. для каждого из идентификаторов, указанных в пунктах импорта:
    1. проверить, есть ли у импортированного модуля атрибут с таким именем
    2. если нет, попытайтесь импортировать подмодуль с этим именем, а затем снова проверьте импортированный модуль на наличие этого атрибута
    3. [...]

Так что from parent import _a будет работать, как и from parent import a, и вы получите подмодуль parent.a и объект 'some string' соответственно.

Обратите внимание, что sys.modules доступен для записи, если вы должны иметь import parent._a работу, вы всегда можете просто изменить sys.modules напрямую:

sys.modules['parent._a'] = sys.modules['parent.a']  # make parent._a an alias for parent.a
import parent._a  # works now
...