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__. Вместо этого они используют пользовательский тип iterable, который будет автоматически выполнять новый поиск частей пакета при следующей попытке импорта внутри этого пакета, если изменится путь их родительского пакета (или 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.

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

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

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

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)
    # Set __loader__ and __package__ if missing.
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(), может быть не тем, который возвращается в конце импорта 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, механизм импорта сам создаст новый модуль.

Добавлено в версии 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: Возникает ошибка DeprecationWarning, когда exec_module() определено, а create_module() - нет.

Изменено в версии 3.6: Возникает ошибка ImportError, когда exec_module() определена, а create_module() - нет.

Изменено в версии 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. Спецификация модуля

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

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

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

Добавлено в версии 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__), механизм импорта попытается сгенерировать репринт из нее. Если это не удается или спецификация отсутствует, система импорта создаст repr по умолчанию, используя любую доступную информацию о модуле. Она попытается использовать module.__name__, module.__file__ и module.__loader__ в качестве входных данных в repr, с настройками по умолчанию для той информации, которая отсутствует.

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

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

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

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

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

Изменено в версии 3.4: Использование loader.module_repr() было отменено, и теперь спецификация модуля используется механизмом импорта для генерации repr модуля.

Для обратной совместимости с Python 3.3, модуль repr будет сгенерирован вызовом метода загрузчика module_repr(), если он определен, перед тем как попробовать любой из описанных выше подходов. Однако этот метод устарел.

Изменено в версии 3.10: Вызов module_repr() теперь происходит после попытки использовать атрибут модуля __spec__, но до возврата к __file__. Использование module_repr() планируется прекратить в Python 3.12.

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 entry finders.

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, заставляя искатель, основанный на пути, снова выполнять поиск записей пути 3.

Если запись пути отсутствует в кэше, то система поиска на основе пути перебирает все вызываемые функции в 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, то используется следующий протокол для запроса спецификации модуля, которая затем используется при загрузке модуля.

Текущий рабочий каталог – обозначаемый пустой строкой – обрабатывается несколько иначе, чем другие записи в 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.

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. Открытые вопросы

XXX Было бы очень хорошо иметь схему.

XXX * (import_machinery.rst) как насчет раздела, посвященного только атрибутам модулей и пакетов, возможно, расширяющего или заменяющего соответствующие записи на справочной странице модели данных?

XXX runpy, pkgutil и др. в руководстве по библиотекам должны получить ссылки «See Also» в верхней части, указывающие на новый раздел о системе импорта.

XXX Добавить больше объяснений относительно различных способов инициализации __main__?

XXX Добавьте больше информации о причудах/недостатках __main__ (т.е. скопируйте из PEP 395).

5.10. Ссылки

Механизм импорта претерпел значительные изменения с первых дней существования 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.

Сноски

1

См. types.ModuleType.

2

Реализация importlib избегает прямого использования возвращаемого значения. Вместо этого она получает объект модуля, просматривая имя модуля в sys.modules. Косвенным следствием этого является то, что импортированный модуль может заменить сам себя в sys.modules. Это специфическое для конкретной реализации поведение, которое не гарантированно будет работать в других реализациях Python.

3

В устаревшем коде можно встретить экземпляры imp.NullImporter в sys.path_importer_cache. Рекомендуется изменить код, чтобы вместо этого использовать None. Более подробную информацию смотрите в Перенос кода Python.

Вернуться на верх