Система импорта
Код 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 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)
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. Спецификация модуля
Механизм импорта использует различные сведения о каждом модуле во время импорта, особенно до загрузки. Большая часть информации общая для всех модулей. Спецификация модуля предназначена для инкапсуляции этой информации, относящейся к импорту, на основе каждого модуля.
Использование спецификации во время импорта позволяет передавать состояние между компонентами системы импорта, например, между поисковиком, создающим спецификацию модуля, и загрузчиком, который его выполняет. Наиболее важно, что это позволяет механизму импорта выполнять рутинные операции по загрузке, в то время как без спецификации модуля загрузчик нес эту ответственность.
Спецификация модуля доступна как атрибут __spec__ объекта модуля. См. ModuleSpec для получения подробной информации о содержимом спецификации модуля.
Добавлена в версии 3.4.
5.4.5. module.__path__
По определению, если у модуля есть атрибут __path__, он является пакетом.
Атрибут __path__ пакета используется при импорте его подпакетов. Внутри механизма импорта он работает примерно так же, как sys.path, т. е. предоставляет список расположений для поиска модулей во время импорта. Однако __path__ обычно намного более ограничен, чем sys.path.
__path__ должен быть итерируемым списком строк, но он может быть пустым. Те же правила, что и для sys.path, также применяются к __path__, а sys.path_hooks (описаны ниже) используются при прохождении по __path__ пакета.
Файл пакета __init__.py может установить или изменить атрибут __path__ пакета, и ранее пакеты пространств имён обычно реализовывались именно так, до PEP 420. С принятием PEP 420 пакеты пространств имён больше не нуждаются в файлах __init__.py, содержащих только код управления __path__; механизм импорта автоматически устанавливает __path__ правильно для пакета пространства имён.
5.4.6. Представления модулей
По умолчанию все модули имеют используемое представление, однако в зависимости от установленных атрибутов и спецификации модуля вы можете более явно управлять представлением объектов модулей.
Если модуль имеет спецификацию (__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.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. Поисковики записей пути
Поисковик, основанный на пути path based finder, отвечает за поиск и загрузку модулей и пакетов Python, расположение которых указано строкой path entry. Большинство записей пути указывают на расположения в файловой системе, но они не ограничиваются этим.
В качестве метапоисковика, поисковик, основанный на пути path based finder, реализует протокол find_spec(), ранее описанный, однако он предоставляет дополнительные крючки, которые можно использовать для настройки того, как модули находятся и загружаются из пути импорта.
Поисковик, основанный на пути path based finder, использует три переменные, sys.path, sys.path_hooks и sys.path_importer_cache. Также используются атрибуты __path__ на объектах пакетов. Они предоставляют дополнительные способы настройки механизма импорта.
sys.path содержит список строк, задающих места поиска модулей и пакетов. Он инициализируется из переменной окружения PYTHONPATH и различных других установок и реализаций по умолчанию. Записи в sys.path могут содержать имена каталогов в файловой системе, zip-файлы и, потенциально, другие «расположения» (см. модуль site), которые необходимо просмотреть для модулей, такие как URL-адреса или запросы к базам данных. В sys.path должны быть только строки; все другие типы данных игнорируются.
Поисковик, основанный на пути path based finder, является метапоисковиком, поэтому механизм импорта начинает поиск по пути импорта, вызывая метод 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 и полностью заменить его пользовательским обработчиком мета-пути.
Если приемлемо только изменить поведение операторов import без изменения других 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. Ссылки
Механизм импорта значительно эволюционировал со времен ранних версий 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 в системе импорта, а также добавляют новые методы для поиска и загрузки.
Примечания
© 2001–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.12/reference/import.html