Spec-Zone.ru › Python 3.13

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

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

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

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

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

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

5.4. Загрузка

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

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

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

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

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

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

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. Спецификации модулей

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

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

Спецификация модуля доступна как module.__spec__. Установка __spec__ соответствующим образом одинаково относится к модулям, инициализированным во время запуска интерпретатора. Единственным исключением является __main__, где __spec__ устанавливается в значение None в некоторых случаях.

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

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

5.4.4. Атрибуты __path__ модулей

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

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

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

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

5.4.5. Представления модулей

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

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

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

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

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

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

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

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

Изменено в версии 3.7: Добавлены файлы кэша с «хеш-базой». Ранее 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, заставляя поисковик по пути выполнить поиск записей пути снова.

Если запись пути отсутствует в кеше, поисковик по пути перебирает все вызываемые объекты в 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().

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

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

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

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

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

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

5.7. Импорты, относительные к пакету

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

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

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

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

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

import XXX.YYY.ZZZ

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

5.8. Особые соображения для __main__

Модуль __main__ является особым случаем относительно системы импорта Python. Как отмечалось в другом месте, модуль __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__, а не при нормальном импорте.

END_OF_DOCUMENT_MARKER

5.9. Справочные материалы

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

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

Spec-Zone.ru

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