Spec-Zone.ru › Python 3.9

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

Код 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. Так, может существовать пакет под названием 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 включает ряд стандартных поисковиков и импортеров. Первый знает, как находить встроенные модули, а второй — как находить модули в формате frozen. Третий стандартный поисковик ищет модули в пути импорта. Путь импорта — это список расположений, которые могут содержать пути к файловой системе или zip-файлы. Он также может быть расширен для поиска любых локализованных ресурсов, таких как те, которые идентифицируются URL-адресами.

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

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

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

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

5.3.3. Обработка импорта

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

Мета-настройки вызываются в начале обработки импорта, до того, как произошла любая другая обработка импорта, кроме поиска в кэше sys.modules. Это позволяет мета-настройкам переопределять обработку sys.path, модулей frozen или даже встроенных модулей. Мета-настройки регистрируются путем добавления новых объектов поисковиков в 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 в качестве второго аргумента.

По умолчанию в sys.meta_path Python три находчика мета-пути: один для импорта встроенных модулей, один для импорта модулей, замороженных во время компиляции, и один для импорта модулей из пути импорта (т. е. находчик на основе путей).

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

5.4. Загрузка

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

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 уже существует объект модуля с данным именем, импорт уже вернёт его.
  • Модуль будет существовать в 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. Подмодули

При загрузке подмодуля любым способом (например, importlib API, 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.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. Представления модулей

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

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

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

  • Если у модуля есть атрибут __spec__, информация в спецификации используется для генерации представления. Используются атрибуты “имя”, “загрузчик”, “исходник” и “имеет_местоположение”.
  • Если у модуля есть атрибут __file__, он используется как часть представления модуля.
  • Если у модуля нет __file__, но есть __loader__, который не None, то представление загрузчика используется как часть представления модуля.
  • В противном случае, просто используйте __name__ в представлении.

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

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

5.4.7. Отмена кэшированного байткода

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

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

Для обратной совместимости с другими реализациями протокола импорта многие поисковики путей входа также поддерживают тот же традиционный метод 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 Было бы здорово иметь диаграмму.

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

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

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

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

END_OF_DOCUMENT_MARKER

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 для получения более подробной информации.

© 2001–2022 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.9/reference/import.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API