Содержание страницы
5. Система импорта¶The import system
Код 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. Пакеты¶Packages
Python has only one type of module object, and all modules are of this type, regardless of whether the module is implemented in Python, C, or something else. To help organize modules and provide a naming hierarchy, Python has a concept of packages.
Можно представлять пакеты как каталоги в файловой системе, а модули – как файлы в этих каталогах, но не стоит воспринимать эту аналогию слишком буквально, поскольку пакеты и модули не обязательно происходят из файловой системы. В целях данной документации мы будем использовать эту удобную аналогию каталогов и файлов. Как и каталоги файловой системы, пакеты организованы иерархически, и пакеты могут содержать вложенные пакеты, а также обычные модули.
Важно помнить, что все пакеты являются модулями, но не все
модули являются пакетами. Или, иными словами, пакеты – это просто особый вид
модуля. В частности, любой модуль, содержащий атрибут __path__, считается
пакетом.
Все модули имеют имя. Имена вложенных пакетов отделяются от имени родительского
пакета точкой, как в стандартном синтаксисе доступа к атрибутам Python. Таким образом,
может существовать пакет email, который содержит вложенный пакет
email.mime, а внутри этого вложенного пакета – модуль email.mime.text.
5.2.1. Обычные пакеты¶Regular packages
Python defines two types of packages, regular packages and namespace packages. Regular
packages are traditional packages as they existed in Python 3.2 and earlier.
A regular package is typically implemented as a directory containing an
__init__.py file. When a regular package is imported, this
__init__.py file is implicitly executed, and the objects it defines are
bound to names in the package’s namespace. The __init__.py file can
contain the same Python code that any other module can contain, and Python
will add some additional attributes to the module when it is imported.
Например, следующая структура файловой системы определяет пакет верхнего уровня 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. Пакеты пространств имен¶Namespace packages
A namespace package is a composite of various portions, where each portion contributes a subpackage to the parent package. Portions may reside in different locations on the file system. Portions may also be found in zip files, on the network, or anywhere else that Python searches during import. Namespace packages may or may not correspond directly to objects on the file system; they may be virtual modules that have no concrete representation.
Пакеты пространств имен не используют обычный список для своего атрибута
__path__. Вместо этого они используют пользовательский итерируемый тип, который автоматически
выполняет новый поиск частей пакета при следующей попытке импорта в рамках
этого пакета, если путь родительского пакета (или sys.path для
пакета верхнего уровня) изменяется.
В пакетах пространств имен нет файла parent/__init__.py. Фактически,
при поиске импорта может быть найдено несколько каталогов parent,
каждый из которых предоставляется разной частью. Таким образом, parent/one может физически
не находиться рядом с parent/two. В этом случае Python создаст
пакет пространства имен для пакета верхнего уровня parent всякий раз, когда импортируется сам этот пакет или любой из его вложенных пакетов.
See also PEP 420 for the namespace package specification.
5.3. Поиск¶Searching
To begin the search, Python needs the fully qualified
name of the module (or package, but for the purposes of this discussion, the
difference is immaterial) being imported. This name may come from various
arguments to the import statement, or from the parameters to the
importlib.import_module() or __import__() functions.
Это имя будет использоваться на различных этапах поиска импорта и может быть точечным путём к подмодулю, например foo.bar.baz. В этом случае Python сначала пытается импортировать foo, затем foo.bar и, наконец, foo.bar.baz. Если какой-либо из промежуточных импортов завершается неудачей, возбуждается ModuleNotFoundError.
5.3.1. Кеш модулей¶The module cache
Первое место, проверяемое при поиске импорта, – это 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. Искатели и загрузчики¶Finders and loaders
Если именованный модуль не найден в sys.modules, то вызывается протокол импорта Python для поиска и загрузки модуля. Этот протокол состоит из двух концептуальных объектов: искателей и загрузчиков. Задача искателя – определить, может ли он найти именованный модуль, используя любую известную ему стратегию. Объекты, реализующие оба этих интерфейса, называются импортерами – они возвращают самих себя, когда обнаруживают, что могут загрузить запрошенный модуль.
Python includes a number of default finders and importers. The first one knows how to locate built-in modules, and the second knows how to locate frozen modules. A third default finder searches an import path for modules. The import path is a list of locations that may name file system paths or zip files. It can also be extended to search for any locatable resource, such as those identified by URLs.
Механизм импорта является расширяемым, поэтому можно добавлять новые искатели для расширения диапазона и области поиска модулей.
Искатели на самом деле не загружают модули. Если они могут найти именованный модуль, они возвращают спецификацию модуля – инкапсуляцию информации, связанной с импортом модуля, которую затем использует механизм импорта при загрузке модуля.
В следующих разделах протокол для искателей и загрузчиков описывается более подробно, включая то, как можно создавать и регистрировать новые для расширения механизма импорта.
Изменено в версии 3.4: В предыдущих версиях Python искатели возвращали загрузчики напрямую, тогда как теперь они возвращают спецификации модулей, которые содержат загрузчики. Загрузчики по-прежнему используются при импорте, но имеют меньше обязанностей.
5.3.3. Хуки импорта¶Import hooks
Механизм импорта разработан как расширяемый; основным механизмом для этого являются хуки импорта. Существует два типа хуков импорта: мета-хуки и хуки пути импорта.
Мета-хуки вызываются в начале обработки импорта, до того, как произойдет любая другая обработка импорта, кроме поиска в кеше sys.modules. Это позволяет мета-хукам переопределять обработку sys.path, замороженных модулей или даже встроенных модулей. Мета-хуки регистрируются путем добавления новых объектов искателей в sys.meta_path, как описано ниже.
Хуки пути импорта вызываются как часть обработки sys.path (или package.__path__) в тот момент, когда встречается связанный с ними элемент пути. Хуки пути импорта регистрируются путем добавления новых вызываемых объектов в sys.path_hooks, как описано ниже.
5.3.4. Мета-путь¶The meta path
Когда именованный модуль не найден в 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.
5.4. Загрузка¶Loading
Если (и когда) спецификация модуля найдена, механизм импорта использует её (и содержащийся в ней загрузчик) при загрузке модуля. Ниже приведено приблизительное описание того, что происходит во время части импорта, связанной с загрузкой:
module = None
if spec.loader is not None and hasattr(spec.loader, 'create_module'):
# Предполагается, что 'exec_module' также будет определён в загрузчике.
module = spec.loader.create_module(spec)
if module is None:
module = ModuleType(spec.name)
# Здесь задаются атрибуты модуля, связанные с импортом:
_init_module_attrs(spec, module)
if spec.loader is None:
# не поддерживается
raise ImportError
if spec.origin is None and spec.submodule_search_locations is not None:
# пакет пространства имён
sys.modules[spec.name] = module
elif not hasattr(spec.loader, 'exec_module'):
module = spec.loader.load_module(spec.name)
# Устанавливает __loader__ и __package__, если они отсутствуют.
else:
sys.modules[spec.name] = module
try:
spec.loader.exec_module(module)
except BaseException:
try:
del sys.modules[spec.name]
except KeyError:
pass
raise
return sys.modules[spec.name]
Обратите внимание на следующие детали:
Если в
sys.modulesуже существует объект модуля с указанным именем, импорт уже вернёт его.Модуль будет существовать в
sys.modulesдо того, как загрузчик выполнит код модуля. Это крайне важно, потому что код модуля может (прямо или косвенно) импортировать сам себя; его добавление вsys.modulesзаранее предотвращает неограниченную рекурсию в худшем случае и множественную загрузку в лучшем.Если загрузка завершается ошибкой, модуль, вызвавший ошибку (и только он), удаляется из
sys.modules. Любой модуль, уже находящийся в кешеsys.modules, а также любой модуль, который был успешно загружен как побочный эффект, должен оставаться в кеше. Это отличается от перезагрузки, где даже неудачно загруженный модуль остаётся вsys.modules.После создания модуля, но до его выполнения, механизм импорта устанавливает атрибуты модуля, связанные с импортом («_init_module_attrs» в примере псевдокода выше), как описано в следующем разделе.
Выполнение модуля – ключевой момент загрузки, в ходе которого заполняется пространство имён модуля. Выполнение полностью делегируется загрузчику, который решает, что и как заполнять.
Модуль, созданный во время загрузки и переданный в exec_module(), может не совпадать с тем, что возвращается в конце импорта [2].
Изменено в версии 3.4: Система импорта взяла на себя стандартные обязанности загрузчиков. Ранее они выполнялись методом importlib.abc.Loader.load_module().
5.4.1. Загрузчики¶Loaders
Загрузчики модулей выполняют критическую функцию загрузки: выполнение модуля. Механизм импорта вызывает метод 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. Подмодули¶Submodules
Когда подмодуль загружается с помощью любого механизма (например, API importlib, операторов import или import-from, или встроенной __import__()), в пространстве имён родительского модуля создаётся связывание с объектом подмодуля. Например, если пакет spam содержит подмодуль foo, то после импорта spam.foo у spam будет атрибут foo, который связан с подмодулем. Допустим, имеется следующая структура каталогов:
spam/
__init__.py
foo.py
и spam/__init__.py содержит следующую строку:
from .foo import Foo
тогда выполнение следующего кода помещает связывания имён для foo и Foo в модуль spam:
>>> import spam
>>> spam.foo
<module 'spam.foo' from '/tmp/imports/spam/foo.py'>
>>> spam.Foo
<class 'spam.foo.Foo'>
Учитывая привычные правила связывания имён в Python, это может показаться удивительным, но на самом деле это фундаментальная особенность системы импорта. Сохраняется следующий инвариант: если имеются sys.modules['spam'] и sys.modules['spam.foo'] (как после импорта выше), то второй должен быть атрибутом foo первого.
5.4.3. Спецификация модуля¶Module spec
Механизм импорта использует различную информацию о каждом модуле во время импорта, особенно перед загрузкой. Большая часть этой информации является общей для всех модулей. Назначение спецификации модуля – инкапсулировать эту связанную с импортом информацию для каждого модуля отдельно.
Использование спецификации во время импорта позволяет передавать состояние между компонентами системы импорта, например, между искателем, который создаёт спецификацию модуля, и загрузчиком, который её выполняет. Самое главное, это позволяет механизму импорта выполнять стандартные операции загрузки, тогда как без спецификации модуля эта ответственность лежала на загрузчике.
Спецификация модуля доступна как атрибут __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. Строковые представления модулей¶Module reprs
По умолчанию все модули имеют пригодное строковое представление, однако в зависимости от установленных выше атрибутов и спецификации модуля можно более явно управлять строковым представлением объектов модулей.
Если у модуля есть спецификация (__spec__), механизм импорта попытается сгенерировать строковое представление на её основе. Если это не удаётся или спецификация отсутствует, система импорта создаст строковое представление по умолчанию, используя любую доступную информацию о модуле. Она попытается использовать module.__name__, module.__file__ и module.__loader__ в качестве входных данных для строкового представления, с умолчаниями для отсутствующей информации.
Ниже приведены точные правила:
Если у модуля есть атрибут
__spec__, для генерации строкового представления используется информация из спецификации. Учитываются атрибуты «name», «loader», «origin» и «has_location».Если у модуля есть атрибут
__file__, он используется как часть строкового представления модуля.Если у модуля нет
__file__, но есть__loader__, который не равенNone, то строковое представление загрузчика используется как часть строкового представления модуля.В противном случае в строковом представлении используется просто
__name__модуля.
Изменено в версии 3.4: Использование loader.module_repr() устарело, и теперь механизм импорта использует спецификацию модуля для генерации представления модуля.
Для обратной совместимости с Python 3.3 представление модуля будет генерироваться вызовом метода module_repr() загрузчика, если он определён, до попытки использования любого из описанных выше подходов. Однако этот метод устарел.
Изменено в версии 3.10: Вызов module_repr() теперь происходит после попытки использования атрибута __spec__ модуля, но до возврата к __file__. Использование module_repr() планируется прекратить в Python 3.12.
5.4.7. Инвалидация кэшированного байт-кода¶Cached bytecode invalidation
Прежде чем Python загрузит кэшированный байт-код из файла .pyc, он проверяет, актуален ли кэш относительно исходного файла .py. По умолчанию Python делает это, сохраняя метку времени последнего изменения и размер исходного файла в кэш-файле при его записи. Во время выполнения система импорта затем проверяет кэш-файл, сверяя сохранённые метаданные в кэш-файле с метаданными исходного файла.
Python также поддерживает кэш-файлы на основе хеша, в которых вместо метаданных хранится хеш содержимого исходного файла. Существует два варианта таких .pyc файлов: проверяемые и непроверяемые. Для проверяемых .pyc файлов Python проверяет кэш-файл, хешируя исходный файл и сравнивая полученный хеш с хешем в кэш-файле. Если проверяемый кэш-файл на основе хеша оказывается недействительным, Python пересоздаёт его и записывает новый проверяемый кэш-файл на основе хеша. Для непроверяемых .pyc файлов Python просто считает кэш-файл действительным, если он существует. Поведение проверки .pyc файлов на основе хеша можно переопределить с помощью флага --check-hash-based-pycs.
Изменено в версии 3.7: Добавлены .pyc файлы на основе хеша. Ранее Python поддерживал только инвалидацию кэшей байт-кода на основе временных меток.
5.5. Искатель на основе путей¶The Path Based Finder
Как упоминалось ранее, Python включает несколько искателей мета-путей по умолчанию. Один из них, называемый искатель на основе путей (PathFinder), ищет в пути импорта, который содержит список записей пути. Каждая запись пути указывает расположение для поиска модулей.
Сам искатель на основе путей не знает, как импортировать что-либо. Вместо этого он обходит отдельные записи пути, связывая каждую из них с искателем записей пути, который знает, как обрабатывать этот конкретный тип пути.
Набор искателей записей пути по умолчанию реализует всю семантику поиска модулей в файловой системе, обрабатывая специальные типы файлов, такие как исходный код Python (файлы .py), байт-код Python (файлы .pyc) и разделяемые библиотеки (например, файлы .so). Если это поддерживается модулем zipimport из стандартной библиотеки, искатели записей пути по умолчанию также загружают все эти типы файлов (кроме разделяемых библиотек) из zip-файлов.
Записи пути не обязаны ограничиваться расположениями в файловой системе. Они могут ссылаться на URL, запросы к базе данных или любые другие расположения, которые можно указать в виде строки.
Искатель на основе путей предоставляет дополнительные перехватчики и протоколы, позволяющие расширять и настраивать типы искомых записей пути. Например, если требуется поддерживать записи пути в виде сетевых URL, можно написать перехватчик, реализующий семантику HTTP для поиска модулей в вебе. Этот перехватчик (вызываемый объект) вернёт искатель записей пути, поддерживающий описанный ниже протокол, который затем будет использован для получения загрузчика модуля из веба.
Предупреждение: в этом и предыдущем разделах используется термин finder (искатель), различая их с помощью терминов искатель мета-путей и искатель записей пути. Эти два типа искателей очень похожи, поддерживают схожие протоколы и действуют схожим образом в процессе импорта, однако важно помнить, что между ними есть тонкие различия. В частности, искатели мета-путей работают в начале процесса импорта, начиная с обхода sys.meta_path.
Напротив, искатели записей пути в некотором смысле являются деталью реализации искателя на основе путей, и на самом деле, если бы искатель на основе путей был удалён из sys.meta_path, ни одна из семантик искателя записей пути не была бы задействована.
5.5.1. Искатели записей пути¶Path entry finders
Искатель на основе путей отвечает за поиск и загрузку модулей и пакетов 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 (несмотря на название, этот кеш на самом деле хранит объекты поисковиков, а не ограничивается объектами importer). Таким образом, дорогой поиск для конкретного местоположения записи пути поисковика записи пути нужно выполнять только один раз. Пользовательский код может свободно удалять записи кеша из sys.path_importer_cache, заставляя поисковик на основе путей выполнить поиск записи пути заново [3].
If the path entry is not present in the cache, the path based finder iterates
over every callable in sys.path_hooks. Each of the path entry
hooks in this list is called with a single argument, the
path entry to be searched. This callable may either return a path
entry finder that can handle the path entry, or it may raise
ImportError. An ImportError is used by the path based finder to
signal that the hook cannot find a path entry finder
for that path entry. The
exception is ignored and import path iteration continues. The hook
should expect either a string or bytes object; the encoding of bytes objects
is up to the hook (e.g. it may be a file system encoding, UTF-8, or something
else), and if the hook cannot decode the argument, it should raise
ImportError.
If sys.path_hooks iteration ends with no path entry finder
being returned, then the path based finder’s
find_spec() method will store None
in sys.path_importer_cache (to indicate that there is no finder for
this path entry) and return None, indicating that this
meta path finder could not find the module.
If a path entry finder is returned by one of the path entry
hook callables on sys.path_hooks, then the following protocol is used
to ask the finder for a module spec, which is then used when loading the
module.
Текущий рабочий каталог – обозначаемый пустой строкой – обрабатывается
несколько иначе, чем другие записи в sys.path. Во-первых, если
текущий рабочий каталог не существует, в sys.path_importer_cache не сохраняется
никакого значения. Во-вторых, значение текущего рабочего каталога
запрашивается заново при каждом поиске модуля. В-третьих, путь,
используемый для sys.path_importer_cache и возвращаемый
importlib.machinery.PathFinder.find_spec(), будет фактическим текущим рабочим каталогом, а не пустой
строкой.
5.5.2. Протокол искателя записей пути¶Path entry finder protocol
Для поддержки импорта модулей и инициализированных пакетов, а также для внесения частей в пространственно-именованные пакеты, искатели записей пути должны реализовывать метод find_spec().
find_spec() принимает два аргумента: полное имя импортируемого модуля и (необязательный) целевой модуль. find_spec() возвращает полностью заполненную спецификацию для модуля. В этой спецификации всегда будет установлен атрибут «loader» (за одним исключением).
Чтобы указать механизму импорта, что спецификация представляет часть пространства имён, поисковик записи пути устанавливает «submodule_search_locations» в список, содержащий эту часть.
Изменено в версии 3.4: find_spec() заменил find_loader() и find_module(), оба из которых теперь устарели, но будут использоваться, если find_spec() не определён.
Более старые искатели записей пути могут реализовывать один из двух устаревших методов вместо find_spec(). Эти методы по-прежнему учитываются для обратной совместимости. Однако, если на искателе записей пути реализован find_spec(), устаревшие методы игнорируются.
find_loader() takes one argument, the
fully qualified name of the module being imported. find_loader()
returns a 2-tuple where the first item is the loader and the second item
is a namespace portion.
Для обратной совместимости с другими реализациями протокола импорта многие искатели записей пути также поддерживают тот же традиционный метод find_module(), который поддерживают искатели метапути. Однако методы find_module() искателей записей пути никогда не вызываются с аргументом path (предполагается, что они записывают соответствующую информацию о пути из первоначального вызова хука пути).
Метод find_module() у искателей записей пути устарел, так как он не позволяет искателю записей пути вносить части в пространственно-именованные пакеты. Если на искателе записей пути существуют и find_loader(), и find_module(), система импорта всегда будет вызывать find_loader() в предпочтение find_module().
Изменено в версии 3.10: Вызовы find_module() и find_loader() системой импорта будут возбуждать ImportWarning.
5.6. Замена стандартной системы импорта¶Replacing the standard import system
Самый надёжный механизм замены всей системы импорта – удалить содержимое по умолчанию из sys.meta_path, полностью заменив их пользовательским хуком метапути.
Если достаточно изменить поведение только инструкций import, не затрагивая другие API, которые обращаются к системе импорта, то может быть достаточно замены встроенной функции __import__(). Этот метод также можно применить на уровне модуля, чтобы изменить поведение инструкций import только в этом модуле.
Чтобы выборочно предотвратить импорт некоторых модулей из хука на раннем этапе метапути (вместо полного отключения стандартной системы импорта), достаточно возбудить ModuleNotFoundError непосредственно из find_spec(), а не возвращать None. Последнее означает, что поиск по метапути должен продолжаться, тогда как возбуждение исключения немедленно его прекращает.
5.7. Относительный импорт в пакетах¶Package Relative Imports
Относительный импорт использует начальные точки. Одна начальная точка означает относительный импорт, начиная с текущего пакета. Две или более начальных точек означают относительный импорт к родительским пакетам текущего пакета, один уровень на каждую точку после первой. Например, при следующей структуре пакета:
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__¶Special considerations for __main__
The __main__ module is a special case relative to Python’s import
system. As noted elsewhere, the __main__ module
is directly initialized at interpreter startup, much like sys and
builtins. However, unlike those two, it doesn’t strictly
qualify as a built-in module. This is because the manner in which
__main__ is initialized depends on the flags and other options with
which the interpreter is invoked.
5.8.1. __main__.__spec__¶
В зависимости от того, как инициализируется __main__, __main__.__spec__ устанавливается соответствующим образом или в None.
Когда Python запускается с опцией -m, __spec__ устанавливается в спецификацию модуля соответствующего модуля или пакета. __spec__ также заполняется, когда модуль __main__ загружается при выполнении каталога, zip-файла или другой записи sys.path.
In the remaining cases
__main__.__spec__ is set to None, as the code used to populate the
__main__ does not correspond directly with an importable module:
интерактивная подсказка
-cопциязапуск из stdin
запуск напрямую из исходного или байт-код файла
Обратите внимание, что __main__.__spec__ всегда None в последнем случае,
даже если файл технически можно импортировать напрямую как модуль.
Используйте флаг -m, если нужны корректные метаданные модуля
в __main__.
Также обратите внимание, что даже если __main__ соответствует импортируемому модулю
и __main__.__spec__ установлено соответствующим образом, они всё равно считаются
отдельными модулями. Это связано с тем, что блоки, защищённые проверками
if __name__ == "__main__":, выполняются только тогда, когда модуль используется
для заполнения пространства имён __main__, а не при обычном импорте.
5.9. Ссылки¶References
Механизм импорта значительно изменился с первых дней 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 в системе импорта, а также добавление новых методов для искателей и загрузчиков.
Сноски
Эта страница – перевод. Оригинал на английском – docs.python.org. Нашли неточность? Сообщите нам.