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

Python код в одном модуле получает доступ к коду в другом модуле в процессе его импорта. Оператор 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 есть концепция пакетов.

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

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

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

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

Python определяет два типа пакетов: обычные пакеты и пакеты пространства имён. Обычные пакеты — это традиционные пакеты, существовавшие в 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. Пакеты пространства имён

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

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

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

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

5.3. Поиск

Чтобы начать поиск, Python необходимо импортировать полностью определенное имя модуля (или пакета, но для целей этого обсуждения разница несущественна). Имя может приходить от различных аргументов оператора 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. Протокол состоит из двух концептуальных объектов: поисковиков и загрузчиков. Задача поисковика — определить, сможет ли он найти требуемый модуль, используя любую известную ему стратегию. Объекты, реализующие оба этих интерфейса, называются импортёрами — они возвращают себя, когда обнаруживают, что могут загрузить запрошенный модуль.

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

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

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

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

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

5.3.3. Хуки импорта

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

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

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

5.3.4. Мета путь

Если требуемый модуль не найден в sys.modules, Python выполняет поиск в sys.meta_path, который содержит список объектов поиска мета-пути. Эти средства поиска опрашиваются, чтобы узнать, знают ли они, как обращаться с указанным модулем. Поисковики мета-пути должны реализовать метод под названием find_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 есть три средства поиска мета-пути: один знает, как импортировать встроенные модули, второй знает, как импортировать замороженные модули, а другой знает, как импортировать модули из путей импорта (то есть поисковик на основе пути).

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

5.4. Загрузка

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

module = None
if spec.loader is not None and hasattr(spec.loader, 'create_module'):
    # Предполагается, что exec_module также будет определен в загрузчике.
    module = spec.loader.create_module(spec)
if module is None:
    module = ModuleType(spec.name)
# Здесь устанавливаются атрибуты модуля, связанные с импортом:
_init_module_attrs(spec, module)

if spec.loader is None:
    # не поддерживается
    raise ImportError
if spec.origin is None and spec.submodule_search_locations is not None:
    # пакет пространства имён
    sys.modules[spec.name] = module
elif not hasattr(spec.loader, 'exec_module'):
    module = spec.loader.load_module(spec.name)
    # Установить __loader__ и __package__ если ошибка.
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 существует объект модуля с данным именем, импорт уже вернул его.
  • Модуль будет существовать в sys.modules до того, как загрузчик выполнит код модуля. Это очень важно, потому что код модуля может (прямо или косвенно) импортировать сам себя; добавление его в sys.modules заранее предотвращает неограниченную рекурсию в худшем случае и множественную загрузку в лучшем.
  • Если загрузка не удалась, неисправный модуль (и только неисправный) удаляется из sys.modules. Любой модуль, уже находящийся в кэше sys.modules, и любой модуль, который был успешно загружен в качестве побочного эффекта, должен оставаться в кэше. Это контрастирует с перезагрузкой, когда даже неисправный модуль остается в sys.modules.
  • После создания модуля, но перед выполнением, механизм импорта устанавливает атрибуты модуля, связанные с импортом («_init_module_attrs» в приведенном выше примере псевдокода), как обобщено в последующем разделе.
  • Выполнение модуля — это ключевой момент загрузки, когда заполняется пространство имён модуля. Выполнение полностью делегируется загрузчику, который решает, что и как заполнять.
  • Модуль, созданный во время загрузки и переданный в 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(), будет также распространено.

Во многих случаях поисковик и загрузчик могут быть одним и тем же объектом; в таких случаях метод find_spec() просто вернёт спецификацию с загрузчиком, установленным на 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() - нет.

5.4.2. Подмодули

Когда подмодуль загружается с использованием любого механизма (например, API importlib, операторов import или import-from или встроенного __import__()), привязка помещается в пространство имён родительского модуля к объекту подмодуля. Например, если в пакете spam есть подмодуль foo, после импорта 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'>

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

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

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

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

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

Добавлено в версии 3.4.

5.4.4. Атрибуты модуля, связанные с импортом

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

__name__

Атрибут __name__ должен быть установлен на полное имя модуля. Это имя используется для уникальной идентификации модуля в системе импорта.

__loader__

Атрибут __loader__ должен быть установлен для объекта загрузчика, который механизм импорта использовал при загрузке модуля. Это в основном для самоанализа, но может использоваться для дополнительных функций, связанных с загрузчиком, например для получения данных связанных с загрузчиком.

__package__

Должен быть установлен атрибут модуля __package__. Его значение должно быть строкой, но может быть таким же, как его __name__. Когда модуль является пакетом, его значение __package__ должно быть установлено равным __name__. Если модуль не является пакетом, в __package__ должна быть установлена ​​пустая строка для модулей верхнего уровня или для подмодулей — имя родительского пакета. См. PEP 366 для получения дополнительной информации.

Атрибут используется вместо __name__ для вычисления явного относительного импорта для основных модулей, как определено в PEP 366. Ожидается, что он будет иметь то же значение, что и __spec__.parent.

Изменено в версии 3.6: Ожидается, что значение __package__ будет таким же, как __spec__.parent.

__spec__

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

Если __package__ не определён, __spec__.parent используется в качестве запасного варианта.

Добавлено в версии 3.4.

Изменено в версии 3.6: __spec__.parent используется как запасной вариант, когда __package__ не определён.

__path__

Если модуль является пакетом (обычным или пространства имён), необходимо установить атрибут __path__ объекта модуля. Значение должно быть итерируемым, но может быть пустым, если __path__ больше не имеет значения. Если __path__ не пуст, он должен создавать строки при итерировании. Более подробная информация о семантике __path__ приведена ниже.

Модули, не входящие в пакет, не должны содержать атрибут __path__.

__file__
__cached__

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

Если установлен __file__, также может быть целесообразно установить атрибут __cached__, который является путём к любой скомпилированной версии кода (например, к байтовому скомпилированному файлу). Для установки этого атрибута необязательно, чтобы файл существовал; путь может просто указывать на место, где должен существовать скомпилированный файл (см. PEP 3147).

Также целесообразно установить __cached__, когда __file__ не установлен. Однако этот сценарий довольно нетипичен. В конечном счете, загрузчик — это то, что использует __file__ и/или __cached__. Поэтому, если загрузчик может загружаться из кэшированного модуля, но в остальном не загружается из файла, этот нетипичный сценарий может быть подходящим.

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

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 поставляется с несколькими поисковиками мета-пути по умолчанию. Один из них, называется поисковиком на основе пути (PathFinder), выполняет поиск в путях импорта, который содержит список путей входа. Каждая запись пути указывает место для поиска модулей.

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

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

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

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

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

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

5.5.1. Поиск путей входа

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

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

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

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

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

Средство поиска на основе пути перебирает каждую запись в пути поиска и для каждой из них ищет соответствующий поисковик пути входа (PathEntryFinder) для записи пути. Поскольку это может быть дорогостоящая операция (например, для этого поиска могут появиться накладные расходы на вызовы stat ()), средство поиска на основе пути поддерживает записи пути отображения кэша для средств поиска записей пути. Кеш поддерживается в sys.path_importer_cache (несмотря на название, этот кеш фактически хранит объекты поиска, а не ограничивается объектами импортера). Таким образом, дорогостоящий поиск поисковик пути входа местоположения пути входа необходимо выполнить только один раз. Пользовательский код может удалить записи кэша из sys.path_importer_cache, заставляя средство поиска на основе пути снова выполнить поиск записи пути [3].

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

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

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

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

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

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

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

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

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

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. Как отмечалось в другом месте, модуль __main__ инициализируется непосредственно при запуске интерпретатора, как и sys и builtins. Однако, в отличие от этих двух, он не может считаться встроенным модулем. Это связано с тем, что способ инициализации __main__ зависит от флагов и других параметров, с которыми вызывается интерпретатор.

5.8.1. __main__.__spec__

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

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

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

  • интерактивное приглашение
  • параметр -c
  • запускаемый из стандартного ввода
  • запускается непосредственно из исходного файла или файла байт-кода

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

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

5.9. Открытые вопросы

ХХХ было бы неплохо иметь схему.

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

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

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

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

5.10. Рекомендации

Механизм импорта значительно изменился с первых дней существования Python. Исходная спецификация пакетов всё ещё доступна для чтения, хотя некоторые детали изменились с момента написания этого документа.

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

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

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

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

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

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

Сноски

[1]См. types.ModuleType.
[2]Реализация importlib избегает прямого использования возвращаемого значения. Вместо этого он получает объект модуля, просматривая имя модуля в sys.modules. Косвенным эффектом этого является то, что импортированный модуль может заменить себя в sys.modules. Это зависящее от реализации поведение, которое не гарантируется для работы в других реализациях Python.
[3]В устаревшем коде можно найти экземпляры imp.NullImporter в sys.path_importer_cache. Рекомендуется изменить код на использование None. См. портирование Python кода для получения более подробной информации.