Страницы

Поиск по вопросам

понедельник, 3 декабря 2018 г.

Неявный импорт в python

Не могу разобраться в системе импортов. Почему когда я делаю:
import os.path
то мне доступен os.walk, который находится в os, хотя я явно не импортирую os? Почему-то со своими package такой финт не прокатывает. За счет чего это происходит?
upd
Я понимаю, что импорт вносит импортируемое в пространство имен модуля. поэтому определив модуль 'myutils.py' c
import requests
в другом модуле получается следущая картина
import myutils resp=myutils.requests.get('http://ya.ru') # работает resp2=requests.get('http://ya.ru') # NameError: name 'requests' is not defined
Есть какая то особенность в механизме import os в os.path? Я понимаю, что os.path есть часть os, а не 2 абсолютно отдельных package, но хочу точно понять механизм.


Ответ

Импорт в Питоне объединяет две операции
найти (загрузить и проинициализировать) модуль ввести новые имена в текущем окружении (как операция присваивания =)
На этапе поиска (пункт №1)—из спецификации import-конструкции
This name will be used in various phases of the import search, and it may be the dotted path to a submodule, e.g. foo.bar.baz. In this case, Python first tries to import foo, then foo.bar, and finally foo.bar.baz. If any of the intermediate imports fail, an ImportError is raised.
то есть import a.b.c импортирует модули a, a.b, a.b.c и более вложенный модуль не может быть импортирован успешно, если импорт неудачен для любого модуля выше.
На этапе присвоения имён (пункт №2)
If the module being imported is not a top level module, then the name of the top level package that contains the module is bound in the local namespace as a reference to the top level package. The imported module must be accessed using its full qualified name rather than directly
import a.b.c в Питоне делает доступным a имя в текущем пространстве имён (например, в глобальном пространстве имён модуля, который импортирует a.b.c) и присваивает атрибут: a.b и атрибут атрибута: a.b.c соответствующим загруженным модулям (аналог: a = sys.modules['a']; a.b = sys.modules['a.b']; ..).
Хотя вопрос с подводхом, потому что os не является Питон-пакетом (__path__ атрибут не установлен) и os.path является обычным атрибутом (таким же как os.walk) с той разницей, что os.path является модулем и os.py содержит хак: sys.modules['os.path'] = path (что разрешает import os.path конструкцию). import os сам по себе уже делает os.path доступным без import os.path

Перефразируя вопрос, используя более регулярный пример:
Почему когда я делаю: import os.pathimport html.parser то мне доступен os.walk html.escape, который находится в oshtml, хотя я явно не импортирую os html?
import html.parser импортирует как html так и (естественно) html.parser модули и так как html/__init__.py (выполняемый на этапе импорта html) определяет escape функцию, то html.escape() также доступна как если бы мы просто выполнили import html
Почему-то со своими package такой финт не прокатывает. За счет чего это происходит?
Если module.name после import module не работает, то это значит что ваш module/__init__.py не определяет name.
В сторону: работоспособность module.name не зависит от __all__. __all__ документирует какие имена являются публичными (формально имена доступные по from module import *), чтобы не добавлять к каждому имени, которое не является частью интерфейса, _ в начале (имена типа _name по умолчанию исключены из from module import *). Но это не запрещает явное обращение такое как module._name (этого следует избегать, но если очень хочется, то можно). В сторону: не используйте from module import * вне REPL или вне __init__.py файла (пример оправданного использования: asyncio/__init__.py—asyncio предоставляет "плоский" публичный интерфейс (имена доступны прямо как asyncio.name), не смотря на то что реализация распределена по многочисленным вложенным модулям).

Комментариев нет:

Отправить комментарий