5. Система импорта

Код Python в одном module получает доступ к коду в другом модуле в процессе importing. Оператор import является наиболее распространенным способом вызова механизма импорта, но это не единственный способ. Функции, такие как importlib.import_module() и встроенные __import__(), также могут быть использованы для вызова механизма импорта.

Оператор import объединяет две операции: он ищет именованный модуль, а затем привязывает результаты поиска к имени в локальной области видимости. Операция поиска в операторе import определяется как вызов функции __import__() с соответствующими аргументами. Возвращаемое значение функции __import__() используется для выполнения операции связывания имен в операторе import. Точные сведения об этой операции связывания имен см. в выражении import.

Прямой вызов __import__() выполняет только поиск модуля и, если он найден, операцию создания модуля. Хотя при этом могут возникать определенные побочные эффекты, такие как импорт родительских пакетов и обновление различных кэшей (включая sys.modules), только оператор import выполняет операцию связывания имен.

При выполнении оператора import вызывается стандартная встроенная функция __import__(). Другие механизмы вызова системы импорта (например, importlib.import_module()) могут обходить __import__() и использовать свои собственные решения для реализации семантики импорта.

Когда модуль импортируется впервые, Python ищет его и, если он найден, создает объект модуля [1], инициализируя его. Если именованный модуль не найден, возникает ошибка ModuleNotFoundError. В Python реализованы различные стратегии поиска именованного модуля при вызове механизма импорта. Эти стратегии могут быть изменены и расширены с помощью различных крючков, описанных в следующих разделах.

Изменено в версии 3.3: Система импорта была обновлена, чтобы полностью реализовать вторую фазу PEP 302. Больше нет неявного механизма импорта - полная система импорта раскрывается через sys.meta_path. Кроме того, была реализована поддержка пакетов нативных пространств имен (см. PEP 420).

5.1. importlib

Модуль importlib предоставляет богатый API для взаимодействия с системой импорта. Например, importlib.import_module() предоставляет рекомендуемый, более простой API, чем встроенный __import__(), для вызова механизма импорта. Дополнительные подробности см. в документации по библиотеке importlib.

5.2. Пакеты

В Python есть только один тип объекта модуля, и все модули относятся к этому типу, независимо от того, реализован ли модуль на Python, C или чем-то еще. Чтобы помочь организовать модули и обеспечить иерархию именования, в Python есть понятие packages.

Можно представить пакеты как каталоги в файловой системе, а модули - как файлы в каталогах, но не стоит воспринимать эту аналогию слишком буквально, поскольку пакеты и модули не обязательно должны возникать из файловой системы. Для целей данной документации мы будем использовать эту удобную аналогию с каталогами и файлами. Как и каталоги файловой системы, пакеты организованы иерархически, и сами пакеты могут содержать подпакеты, а также обычные модули.

Важно помнить, что все пакеты - это модули, но не все модули - это пакеты. Или, говоря иначе, пакеты - это просто особый вид модулей. В частности, любой модуль, содержащий атрибут __path__, считается пакетом.

Все модули имеют имя. Имена подпакетов отделяются от имени родительского пакета точкой, что напоминает стандартный синтаксис доступа к атрибутам в Python. Таким образом, у вас может быть пакет email, который, в свою очередь, имеет подпакет email.mime и модуль внутри этого подпакета email.mime.text.

5.2.1. Обычные пакеты

В Python определены два типа пакетов, regular packages и namespace packages. Обычные пакеты - это традиционные пакеты в том виде, в котором они существовали в Python 3.2 и более ранних версиях. Обычный пакет обычно реализуется как каталог, содержащий __init__.py файл. При импорте регулярного пакета этот __init__.py файл неявно выполняется, а определяемые в нем объекты привязываются к именам в пространстве имен пакета. Файл __init__.py может содержать тот же код Python, что и любой другой модуль, а при импорте Python добавит модулю некоторые дополнительные атрибуты.

Например, следующая схема файловой системы определяет пакет верхнего уровня parent с тремя подпакетами:

parent/
    __init__.py
    one/
        __init__.py
    two/
        __init__.py
    three/
        __init__.py

Импорт parent.one неявно приведет к выполнению parent/__init__.py и parent/one/__init__.py. Последующий импорт parent.two или parent.three приведет к выполнению parent/two/__init__.py и parent/three/__init__.py соответственно.

5.2.2. Пакеты пространства имен

Пакет пространства имен - это совокупность различных portions, где каждая часть вносит свой вклад в родительский пакет. Части могут находиться в разных местах файловой системы. Части также могут находиться в zip-файлах, в сети или в любом другом месте, которое Python ищет при импорте. Пакеты пространства имен могут напрямую соответствовать или не соответствовать объектам в файловой системе; они могут быть виртуальными модулями, не имеющими конкретного представления.

Пакеты пространства имен не используют обычный список для своего атрибута __path__. Вместо этого они используют пользовательский итерируемый тип, который будет автоматически выполнять новый поиск порций пакета при следующей попытке импорта внутри этого пакета, если изменится путь к их родительскому пакету (или sys.path для пакета верхнего уровня).

В пакетах пространства имен нет файла parent/__init__.py. На самом деле, при поиске импорта может быть найдено несколько каталогов parent, каждый из которых предоставляется отдельной порцией. Таким образом, parent/one может физически не располагаться рядом с parent/two. В этом случае Python будет создавать пакет пространства имен для пакета верхнего уровня parent всякий раз, когда будет импортирован этот пакет или один из его подпакетов.

См. также PEP 420 для спецификации пакета пространства имен.

5.3. Поиск

Чтобы начать поиск, Python необходимо знать fully qualified имя импортируемого модуля (или пакета, но для целей данного обсуждения разница несущественна). Это имя может быть получено из различных аргументов оператора import или из параметров функций importlib.import_module() или __import__().

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

5.3.1. Кэш модуля

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

Во время импорта имя модуля ищется в sys.modules, и если оно присутствует, то связанное с ним значение является модулем, удовлетворяющим импорту, и процесс завершается. Однако если значение равно None, то возникает ошибка ModuleNotFoundError. Если имя модуля отсутствует, Python продолжит поиск модуля.

sys.modules является доступным для записи. Удаление ключа не уничтожает связанный с ним модуль (поскольку другие модули могут содержать ссылки на него), но аннулирует запись в кэше для именованного модуля, заставляя Python заново искать именованный модуль при следующем импорте. Ключу также можно присвоить значение None, что заставит следующий импорт модуля привести к появлению ModuleNotFoundError.

Однако будьте осторожны: если вы сохраните ссылку на объект модуля, аннулируете запись в его кэше в sys.modules, а затем повторно импортируете именованный модуль, два объекта модуля не будут одинаковыми. Напротив, importlib.reload() повторно использует тот же объект модуля и просто заново инициализирует содержимое модуля, повторно выполнив его код.

5.3.2. Поисковики и погрузчики

Если именованный модуль не найден в sys.modules, то для его поиска и загрузки вызывается протокол импорта Python. Этот протокол состоит из двух концептуальных объектов, finders и loaders. Задача finder’а - определить, может ли он найти именованный модуль, используя любую известную ему стратегию. Объекты, реализующие оба этих интерфейса, называются importers. - Они возвращают себя, когда обнаруживают, что могут загрузить запрошенный модуль.

Python включает в себя несколько стандартных поисковиков и импортеров. Первый знает, как найти встроенные модули, а второй - как найти замороженные модули. Третий поисковик по умолчанию ищет модули в import path. В качестве import path используется список местоположений, в котором могут быть указаны пути к файловой системе или zip-файлам. Он также может быть расширен для поиска любого локализуемого ресурса, например, идентифицируемого URL.

Механизм импорта является расширяемым, поэтому можно добавлять новые средства поиска, чтобы расширить диапазон и область поиска модулей.

Поисковики на самом деле не загружают модули. Если им удается найти именованный модуль, они возвращают module spec, инкапсуляцию связанной с импортом модуля информации, которую механизм импорта использует при загрузке модуля.

В следующих разделах более подробно описывается протокол для искателей и загрузчиков, включая то, как вы можете создавать и регистрировать новые для расширения механизма импорта.

Изменено в версии 3.4: В предыдущих версиях Python поисковики возвращали loaders напрямую, тогда как сейчас они возвращают спецификации модулей, которые содержат загрузчики. Загрузчики по-прежнему используются при импорте, но у них меньше обязанностей.

5.3.3. Импортные крючки

Механизм импорта разработан таким образом, чтобы быть расширяемым; основным механизмом для этого являются импортные крючки. Существует два типа крючков импорта: крючки меты и крючки пути импорта.

Мета-хуки вызываются в начале обработки импорта, до того, как произошла любая другая обработка импорта, кроме поиска кэша sys.modules. Это позволяет мета-хукам перекрывать обработку sys.path, замороженные модули или даже встроенные модули. Мета-хуки регистрируются путем добавления новых объектов finder в sys.meta_path, как описано ниже.

Крючки пути импорта вызываются в процессе обработки sys.path (или package.__path__), в момент, когда встречается связанный с ними элемент пути. Крючки пути импорта регистрируются путем добавления новых вызываемых элементов в sys.path_hooks, как описано ниже.

5.3.4. Мета-путь

Если именованный модуль не найден в sys.modules, Python обращается к sys.meta_path, который содержит список объектов поиска мета-пути. Эти искатели запрашиваются на предмет того, знают ли они, как работать с именованным модулем. Метапоисковики должны реализовать метод find_spec(), который принимает три аргумента: имя, путь импорта и (опционально) целевой модуль. Метапоисковик может использовать любую стратегию, чтобы определить, может ли он работать с именованным модулем или нет.

Если программа поиска метапути знает, как работать с именованным модулем, она возвращает объект spec. Если он не может обработать именованный модуль, то возвращается None. Если обработка sys.meta_path достигает конца своего списка, не возвращая спецификацию, то поднимается сообщение ModuleNotFoundError. Любые другие возникшие исключения просто распространяются вверх, прерывая процесс импорта.

Метод find_spec() метапоиска путей вызывается с двумя или тремя аргументами. Первый - это полное имя импортируемого модуля, например foo.bar.baz. Второй аргумент - записи пути, которые следует использовать для поиска модуля. Для модулей верхнего уровня вторым аргументом является None, а для подмодулей или подпакетов вторым аргументом будет значение атрибута __path__ родительского пакета. Если доступ к соответствующему атрибуту __path__ невозможен, возникает ошибка ModuleNotFoundError. Третий аргумент - это существующий объект модуля, который будет загружен позже. Система импорта передает целевой модуль только при перезагрузке.

Мета-путь может быть пройден несколько раз для одного запроса импорта. Например, если предположить, что ни один из задействованных модулей еще не был кэширован, то при импорте foo.bar.baz сначала будет выполнен импорт верхнего уровня с вызовом mpf.find_spec("foo", None, None) на каждом мета-пути поиска (mpf). После того как foo будет импортирован, foo.bar будет импортирован путем обхода метапути во второй раз, вызывая mpf.find_spec("foo.bar", foo.__path__, None). После импорта foo.bar будет вызван mpf.find_spec("foo.bar.baz", foo.bar.__path__, None).

Некоторые программы поиска метапутей поддерживают импорт только верхнего уровня. Такие импортеры всегда будут возвращать None, если в качестве второго аргумента передается что-либо, отличное от None.

В стандартном sys.meta_path Python есть три мета-поисковика путей: один знает, как импортировать встроенные модули, один знает, как импортировать замороженные модули, и один знает, как импортировать модули из import path (то есть из path based finder).

Изменено в версии 3.4: Метод find_spec() в метапоисковиках путей заменил find_module(), который теперь устарел. Хотя он будет продолжать работать без изменений, механизм импорта будет использовать его только в том случае, если искатель не реализует find_spec().

Изменено в версии 3.10: Использование find_module() системой импорта теперь повышает ImportWarning.

Изменено в версии 3.12: find_module() был удален. Вместо этого используйте find_spec().

5.4. Загрузка

Если и когда спецификация модуля будет найдена, механизм импорта будет использовать ее (и содержащийся в ней загрузчик) при загрузке модуля. Вот примерная картина того, что происходит во время загрузки при импорте:

module = None
if spec.loader is not None and hasattr(spec.loader, 'create_module'):
    # It is assumed 'exec_module' will also be defined on the loader.
    module = spec.loader.create_module(spec)
if module is None:
    module = ModuleType(spec.name)
# The import-related module attributes get set here:
_init_module_attrs(spec, module)

if spec.loader is None:
    # unsupported
    raise ImportError
if spec.origin is None and spec.submodule_search_locations is not None:
    # namespace package
    sys.modules[spec.name] = module
elif not hasattr(spec.loader, 'exec_module'):
    module = spec.loader.load_module(spec.name)
else:
    sys.modules[spec.name] = module
    try:
        spec.loader.exec_module(module)
    except BaseException:
        try:
            del sys.modules[spec.name]
        except KeyError:
            pass
        raise
return sys.modules[spec.name]

Обратите внимание на следующие детали:

  • Если в sys.modules есть существующий объект модуля с заданным именем, import уже вернет его.

  • Модуль будет существовать в sys.modules до того, как загрузчик выполнит код модуля. Это очень важно, потому что код модуля может (прямо или косвенно) импортировать сам себя; добавление его в sys.modules заранее предотвращает беспредельную рекурсию в худшем случае и множественную загрузку в лучшем.

  • Если загрузка не удалась, неудачный модуль - и только неудачный модуль - удаляется из sys.modules. Любой модуль, уже находящийся в кэше sys.modules, и любой модуль, который был успешно загружен в качестве побочного эффекта, должны остаться в кэше. Это отличается от перезагрузки, при которой даже сбойный модуль остается в sys.modules.

  • После создания модуля, но до его выполнения, механизм импорта устанавливает связанные с импортом атрибуты модуля («_init_module_attrs» в приведенном выше примере псевдокода), как указано в later section.

  • Выполнение модуля - это ключевой момент загрузки, в который происходит заполнение пространства имен модуля. Выполнение полностью делегируется загрузчику, который решает, что и как будет заполнено.

  • Модуль, созданный во время загрузки и переданный в exec_module(), может отличаться от модуля, возвращаемого в конце операции import [2].

Изменено в версии 3.4: Система импорта взяла на себя обязанности загрузчиков. Ранее их выполнял метод importlib.abc.Loader.load_module().

5.4.1. Погрузчики

Загрузчики модулей обеспечивают важнейшую функцию загрузки: выполнение модуля. Механизм импорта вызывает метод importlib.abc.Loader.exec_module() с единственным аргументом - объектом модуля для выполнения. Любое значение, возвращаемое из exec_module(), игнорируется.

Погрузчики должны отвечать следующим требованиям:

  • Если модуль является модулем Python (в отличие от встроенного модуля или динамически загружаемого расширения), загрузчик должен выполнить код модуля в глобальном пространстве имен модуля (module.__dict__).

  • Если загрузчик не может выполнить модуль, он должен поднять ImportError, хотя любое другое исключение, поднятое во время exec_module(), будет передано.

Во многих случаях finder и loader могут быть одним и тем же объектом; в таких случаях метод find_spec() просто вернет spec с loader, установленным в self.

Загрузчики модулей могут отказаться от создания объекта модуля во время загрузки, реализовав метод create_module(). Он принимает один аргумент, спецификацию модуля, и возвращает новый объект модуля для использования во время загрузки. Метод create_module() не требует установки каких-либо атрибутов для объекта модуля. Если метод возвращает None, механизм импорта сам создаст новый модуль.

Added in version 3.4: Метод create_module() для загрузчиков.

Изменено в версии 3.4: Метод load_module() был заменен на exec_module(), а механизм импорта взял на себя все обязанности по загрузке.

Для совместимости с существующими загрузчиками механизм импорта будет использовать метод load_module() загрузчиков, если он существует и загрузчик не реализует также exec_module(). Однако метод load_module() был устаревшим, и вместо него загрузчики должны реализовывать exec_module().

Метод load_module() должен реализовывать всю функциональность загрузки, описанную выше, в дополнение к выполнению модуля. Применяются все те же ограничения, но с некоторыми дополнительными уточнениями:

  • Если в sys.modules есть существующий объект модуля с заданным именем, загрузчик должен использовать этот существующий модуль. (В противном случае importlib.reload() будет работать некорректно.) Если названный модуль не существует в sys.modules, загрузчик должен создать новый объект модуля и добавить его в sys.modules.

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

  • Если загрузка не удалась, загрузчик должен удалить все модули, которые он вставил в sys.modules, но он должен удалить только неудачный модуль (модули), и только если сам загрузчик загрузил модуль (модули) явным образом.

Изменено в версии 3.5: Если exec_module() определен, а create_module() - нет, то ставится DeprecationWarning.

Изменено в версии 3.6: Если exec_module() определен, а create_module() - нет, то появляется символ ImportError.

Изменено в версии 3.10: Использование load_module() приведет к повышению ImportWarning.

5.4.2. Субмодули

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

spam/
    __init__.py
    foo.py

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

from .foo import Foo

то выполнение следующей команды устанавливает привязки имен для foo и Foo в модуле spam:

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

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

5.4.3. Спецификация модуля

Механизм импорта использует разнообразную информацию о каждом модуле во время импорта, особенно перед загрузкой. Большая часть этой информации является общей для всех модулей. Цель спецификации модуля - инкапсулировать эту информацию, связанную с импортом, для каждого модуля.

Использование спецификации при импорте позволяет передавать состояние между компонентами системы импорта, например, между finder, который создает спецификацию модуля, и loader, который ее выполняет. Самое главное, это позволяет механизму импорта выполнять шаблонные операции загрузки, в то время как без спецификации модуля эта обязанность лежала на загрузчике.

Спецификация модуля отображается как атрибут __spec__ на объекте модуля. Подробнее о содержимом спецификации модуля см. в разделе ModuleSpec.

Added in version 3.4.

5.4.5. module.__path__

По определению, если модуль имеет атрибут __path__, он является пакетом.

Атрибут __path__ пакета используется при импорте его подпакетов. В механизме импорта он функционирует так же, как и sys.path, т. е. предоставляет список мест для поиска модулей при импорте. Однако __path__, как правило, имеет гораздо больше ограничений, чем sys.path.

__path__ должен быть итератором строк, но может быть и пустым. Те же правила, которые используются для sys.path, применяются и к __path__ пакета, а sys.path_hooks (описанные ниже) используются при обходе __path__ пакета.

Файл __init__.py пакета может устанавливать или изменять атрибут __path__ пакета, и именно так обычно реализовывались пакеты пространства имен до появления PEP 420. С принятием PEP 420 пакетам пространства имен больше не нужно поставлять файлы __init__.py, содержащие только код манипулирования __path__; механизм импорта автоматически устанавливает __path__ корректно для пакета пространства имен.

5.4.6. Модуль reprs

По умолчанию все модули имеют полезный repr, однако в зависимости от атрибутов, заданных выше и в спецификации модуля, вы можете более явно контролировать repr объектов модуля.

Если у модуля есть спецификация (__spec__), механизм импорта попытается сгенерировать из нее репризу. Если это не удается или спецификация отсутствует, система импорта создаст репрезентацию по умолчанию, используя любую доступную информацию о модуле. Она попытается использовать module.__name__, module.__file__ и module.__loader__ в качестве исходных данных для repr, а для недостающей информации будет задана информация по умолчанию.

Вот точные правила, которые были использованы:

  • Если модуль имеет атрибут __spec__, для создания repr используется информация из спецификации. При этом используются атрибуты «name», «loader», «origin» и «has_location».

  • Если у модуля есть атрибут __file__, он используется как часть repr модуля.

  • Если модуль не имеет __file__, но имеет __loader__, который не является None, то repr загрузчика используется как часть repr модуля.

  • В противном случае просто используйте __name__ модуля в повторе.

Изменено в версии 3.12: Использование module_repr(), которое было устаревшим с Python 3.4, было удалено в Python 3.12 и больше не вызывается при разрешении repr модуля.

5.4.7. Устранение недействительности кэшированного байткода

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

Python также поддерживает «хэш-ориентированные» файлы кэша, которые хранят хэш содержимого исходного файла, а не его метаданные. Существует два варианта хэш-файлов .pyc: проверенные и непроверенные. Для файлов .pyc, основанных на проверенном хэше, Python проверяет файл кэша, хэшируя исходный файл и сравнивая полученный хэш с хэшем в файле кэша. Если файл кэша на основе проверенного хэша оказывается недействительным, Python перегенерирует его и записывает новый файл кэша на основе проверенного хэша. Для непроверенных файлов .pyc на основе хэша Python просто принимает файл кэша за действительный, если он существует. Поведение проверки файлов на основе хэша .pyc может быть переопределено с помощью флага --check-hash-based-pycs.

Изменено в версии 3.7: Добавлены файлы .pyc на основе хэша. Ранее Python поддерживал только аннулирование кэша байткода по временной метке.

5.5. Поисковик на основе пути

Как уже упоминалось, Python поставляется с несколькими метапоисковиками путей по умолчанию. Один из них, называемый path based finder (PathFinder), ищет путь import path, который содержит список path entries. Каждый элемент пути называет место для поиска модулей.

Сама программа поиска пути не знает, как импортировать что-либо. Вместо этого он просматривает отдельные записи пути, связывая каждую из них с поисковиком, который знает, как обрабатывать этот конкретный вид пути.

Набор средств поиска входов по умолчанию реализует всю семантику поиска модулей в файловой системе, обрабатывая специальные типы файлов, такие как исходный код Python (.py файлы), байт-код Python (.pyc файлы) и разделяемые библиотеки (например, .so файлы). При поддержке модуля zipimport в стандартной библиотеке стандартные средства поиска путей по умолчанию также обрабатывают загрузку всех этих типов файлов (кроме общих библиотек) из zip-файлов.

Записи пути не обязательно должны ограничиваться расположением файловой системы. Они могут ссылаться на URL-адреса, запросы к базам данных или любые другие места, которые можно указать в виде строки.

Средство поиска по пути предоставляет дополнительные хуки и протоколы, чтобы вы могли расширять и настраивать типы записей пути для поиска. Например, если вы хотите поддерживать записи пути как сетевые URL, вы можете написать хук, реализующий семантику HTTP для поиска модулей в Интернете. Этот хук (вызываемый модуль) возвращал бы path entry finder, поддерживающий протокол, описанный ниже, который затем использовался бы для получения загрузчика модуля из сети.

Предупреждение: и в этом, и в предыдущем разделе используется термин искатель, а для различия между ними используются термины meta path finder и path entry finder. Эти два типа искателей очень похожи, поддерживают схожие протоколы и функционируют схожим образом в процессе импорта, но важно помнить, что они имеют существенные различия. В частности, метапоисковики путей работают в начале процесса импорта, как и ключ обхода sys.meta_path.

В отличие от этого, поиск вхождения в путь в некотором смысле является деталью реализации поиска на основе пути, и на самом деле, если бы поиск на основе пути был удален из sys.meta_path, ни одна из семантик поиска вхождения в путь не была бы задействована.

5.5.1. Поисковики для ввода пути

Путь path based finder отвечает за поиск и загрузку модулей и пакетов Python, местоположение которых указано строкой path entry. Большинство записей пути называют местоположение в файловой системе, но не обязательно ограничиваться только этим.

Как мета-поисковик путей, path based finder реализует протокол find_spec(), описанный ранее, однако он открывает дополнительные крючки, которые могут быть использованы для настройки того, как модули будут найдены и загружены из import path.

Используются три переменные path based finder, sys.path, sys.path_hooks и sys.path_importer_cache. Также используются атрибуты __path__ на объектах пакетов. Они обеспечивают дополнительные возможности настройки механизма импорта.

sys.path содержит список строк, указывающих места поиска модулей и пакетов. Он инициализируется из переменной окружения PYTHONPATH и различных других настроек по умолчанию, зависящих от установки и реализации. Записи в sys.path могут называть каталоги в файловой системе, zip-файлы и, возможно, другие «места» (см. модуль site), в которых следует искать модули, например URL или запросы к базе данных. В модуле sys.path должны присутствовать только строки; все остальные типы данных игнорируются.

В качестве path based finder используется meta path finder, поэтому механизм импорта начинает поиск import path, вызывая метод find_spec(), основанный на поиске пути, как было описано ранее. Если указан аргумент path для find_spec(), то это будет список строковых путей для обхода - обычно это атрибут __path__ пакета для поиска импорта в этом пакете. Если аргументом path является None, это указывает на импорт верхнего уровня и используется sys.path.

Программа поиска на основе пути перебирает все записи в пути поиска и для каждой из них ищет соответствующий path entry finder (PathEntryFinder) для записи пути. Поскольку это может быть дорогостоящей операцией (например, могут быть накладные расходы на вызов stat() для этого поиска), программа поиска на основе пути поддерживает кэш, отображающий записи пути на записи поиска пути. Этот кэш хранится в sys.path_importer_cache (несмотря на название, этот кэш на самом деле хранит объекты искателя, а не ограничивается объектами importer). Таким образом, дорогостоящий поиск конкретного path entry местоположения path entry finder выполняется только один раз. Пользовательский код может свободно удалять записи кэша из sys.path_importer_cache, заставляя искатель, основанный на пути, снова выполнять поиск записей пути.

Если элемент пути отсутствует в кэше, то программа поиска на основе пути перебирает все вызываемые элементы в sys.path_hooks. Каждый из path entry hooks в этом списке вызывается с единственным аргументом - записью пути, которую нужно найти. Этот вызываемый элемент может либо вернуть path entry finder, который может обработать запись пути, либо вызвать ImportError. Исключение ImportError используется механизмом поиска пути для сигнализации о том, что хук не может найти path entry finder для данного path entry. Исключение игнорируется, и итерация import path продолжается. Хук должен ожидать либо строку, либо объект bytes; кодировка объектов bytes зависит от хука (например, это может быть кодировка файловой системы, UTF-8 или что-то еще), и если хук не может декодировать аргумент, он должен поднять ImportError.

Если итерация sys.path_hooks заканчивается без возврата path entry finder, то метод find_spec() метода поиска на основе пути сохранит None в sys.path_importer_cache (чтобы указать, что для этого элемента пути нет искателя) и вернет None, указывая, что этот meta path finder не смог найти модуль.

Если path entry finder *возвращается одной из вызываемых path entry hook на sys.path_hooks, то используется следующий протокол, чтобы запросить у finder спецификацию модуля, которая затем используется при загрузке модуля.

Текущий рабочий каталог - обозначаемый пустой строкой - обрабатывается несколько иначе, чем другие записи в sys.path. Во-первых, если окажется, что текущий рабочий каталог не существует, значение в sys.path_importer_cache не сохраняется. Во-вторых, значение текущей рабочей директории ищется заново для каждого поиска модуля. В-третьих, путь, используемый для sys.path_importer_cache и возвращаемый importlib.machinery.PathFinder.find_spec(), будет реальным текущим рабочим каталогом, а не пустой строкой.

5.5.2. Протокол поиска входа в путь

Для того чтобы поддерживать импорт модулей и инициализированных пакетов, а также вносить части в пакеты пространства имен, средства поиска путей должны реализовывать метод find_spec().

find_spec() принимает два аргумента: полное имя импортируемого модуля и (необязательно) целевой модуль. find_spec() возвращает полностью заполненную спецификацию модуля. В этой спецификации всегда будет установлен «loader» (за одним исключением).

Чтобы указать механизму импорта, что спецификация представляет собой пространство имен portion, средство поиска вхождения в путь устанавливает submodule_search_locations в список, содержащий эту часть.

Изменено в версии 3.4: find_spec() заменил find_loader() и find_module(), которые теперь устарели, но будет использоваться, если find_spec() не определен.

Старые программы поиска входа в путь могут реализовать один из этих двух устаревших методов вместо find_spec(). Эти методы по-прежнему поддерживаются для обеспечения обратной совместимости. Однако если в устройстве поиска пути реализован find_spec(), устаревшие методы игнорируются.

find_loader() принимает один аргумент - полное имя импортируемого модуля. find_loader() возвращает кортеж из двух элементов, первый из которых - загрузчик, а второй - пространство имен portion.

Для обратной совместимости с другими реализациями протокола импорта многие средства поиска входа в путь также поддерживают тот же традиционный метод find_module(), который поддерживают средства поиска метапути. Однако методы find_module() никогда не вызываются с аргументом path (предполагается, что они записывают соответствующую информацию о пути при первоначальном вызове крючка пути).

Метод find_module() для поиска путей устарел, поскольку не позволяет ему вносить части в пакеты пространства имен. Если и find_loader(), и find_module() существуют в устройстве поиска путей, система импорта всегда будет вызывать find_loader(), предпочитая find_module().

Изменено в версии 3.10: Вызовы find_module() и find_loader() системой импорта поднимают ImportWarning.

Изменено в версии 3.12: find_module() и find_loader() были удалены.

5.6. Замена стандартной системы импорта

Самый надежный механизм замены всей системы импорта - удалить содержимое sys.meta_path по умолчанию, полностью заменив его пользовательским хуком мета-пути.

Если допустимо изменить только поведение операторов импорта, не затрагивая другие API, которые обращаются к системе импорта, то замены встроенной функции __import__() может быть достаточно. Эта техника также может быть использована на уровне модуля, чтобы изменять поведение операторов импорта только в пределах этого модуля.

Чтобы выборочно запретить импорт некоторых модулей из хука на ранней стадии метапути (а не отключать стандартную систему импорта полностью), достаточно поднять ModuleNotFoundError непосредственно из find_spec(), а не возвращать None. Последнее указывает на то, что поиск мета-пути должен быть продолжен, в то время как вызов исключения немедленно его завершает.

5.7. Относительный импорт пакетов

При относительном импорте используются ведущие точки. Одна ведущая точка указывает на относительный импорт, начиная с текущего пакета. Две или более ведущих точек указывают на относительный импорт к родительскому(им) пакету(ам) текущего пакета, по одному уровню за каждую точку после первой. Например, при следующем расположении пакетов:

package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
        moduleY.py
    subpackage2/
        __init__.py
        moduleZ.py
    moduleA.py

В subpackage1/moduleX.py или subpackage1/__init__.py действительными относительными импортами являются следующие:

from .moduleY import spam
from .moduleY import spam as ham
from . import moduleY
from ..subpackage1 import moduleY
from ..subpackage2.moduleZ import eggs
from ..moduleA import foo

Абсолютные импорты могут использовать синтаксис import <> или from <> import <>, но относительные импорты могут использовать только вторую форму; причина этого в том, что:

import XXX.YYY.ZZZ

должен отображать XXX.YYY.ZZZ как допустимое выражение, но .moduleY не является допустимым выражением.

5.8. Специальные соображения для __main__

Модуль __main__ - это особый случай по отношению к системе импорта Python. Как уже отмечалось elsewhere, модуль __main__ напрямую инициализируется при запуске интерпретатора, как и sys и builtins. Однако, в отличие от этих двух модулей, он не может строго считаться встроенным модулем. Это связано с тем, что способ инициализации __main__ зависит от флагов и других опций, с которыми вызывается интерпретатор.

5.8.1. __main__.__spec__

В зависимости от того, как инициализируется __main__, __main__.__spec__ устанавливается соответствующим образом или на None.

Когда Python запускается с опцией -m, __spec__ устанавливается в спецификацию модуля соответствующего модуля или пакета. __spec__ также заполняется, когда модуль __main__ загружается при выполнении каталога, zip-файла или другого sys.path элемента.

В the remaining cases __main__.__spec__ установлено значение None, так как код, используемый для заполнения __main__, не соответствует непосредственно импортируемому модулю:

  • интерактивная подсказка

  • -c вариант

  • запуск из stdin

  • запуск непосредственно из файла исходного кода или байткода

Обратите внимание, что в последнем случае __main__.__spec__ всегда будет None, даже если файл технически может быть импортирован непосредственно как модуль. Используйте переключатель -m, если в __main__ требуются корректные метаданные модуля.

Обратите внимание, что даже если __main__ соответствует импортируемому модулю, а __main__.__spec__ установлен соответствующим образом, они все равно считаются отдельными модулями. Это связано с тем, что блоки, защищенные проверками if __name__ == "__main__":, выполняются только тогда, когда модуль используется для заполнения пространства имен __main__, а не во время обычного импорта.

5.9. Ссылки

Механизм импорта претерпел значительные изменения с первых дней существования Python. Оригинальный specification for packages все еще доступен для чтения, хотя некоторые детали изменились с момента написания этого документа.

Первоначальная спецификация для sys.meta_path была PEP 302, с последующим расширением в PEP 420.

PEP 420 представил namespace packages для Python 3.3. PEP 420 также представил протокол find_loader() в качестве альтернативы find_module().

PEP 366 описывает добавление атрибута __package__ для явного относительного импорта в основных модулях.

PEP 328 ввел абсолютный и явный относительный импорт и первоначально предложил __name__ для семантики PEP 366, которая в конечном итоге будет определена для __package__.

PEP 338 определяет выполнение модулей как скриптов.

PEP 451 добавляет инкапсуляцию состояния импорта каждого модуля в объектах spec. Он также снимает с загрузчиков большую часть обязанностей, связанных с котлами, и возвращает их в механизм импорта. Эти изменения позволили устареть нескольким API в системе импорта, а также добавить новые методы в finders и loaders.

Сноски