5. Система импорта¶
Код Python в одном module получает доступ к коду в другом модуле с помощью importing. Оператор import
является наиболее распространенным способом вызова механизма импорта, но это не единственный способ. Такие функции, как importlib.import_module()
и встроенные __import__()
, также могут быть использованы для вызова механизма импорта.
Оператор import
объединяет две операции; он выполняет поиск именованного модуля, а затем привязывает результаты этого поиска к имени в локальной области видимости. Операция поиска в инструкции import
определяется как вызов функции __import__()
с соответствующими аргументами. Возвращаемое значение __import__()
используется для выполнения операции привязки имени в инструкции import
. Подробные сведения об этой операции привязки имени приведены в инструкции import
.
Прямой вызов __import__()
выполняет только поиск модуля и, если он найден, операцию создания модуля. Хотя могут возникать определенные побочные эффекты, такие как импорт родительских пакетов и обновление различных кэшей (включая sys.modules
), только оператор import
выполняет операцию привязки имени.
При выполнении инструкции import
вызывается стандартная встроенная функция __import__()
. Другие механизмы для вызова системы импорта (например, importlib.import_module()
) могут обойти __import__()
и использовать свои собственные решения для реализации семантики импорта.
При первом импорте модуля Python выполняет поиск модуля и, если он найден, создает объект module [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 есть только один тип объекта module, и все модули относятся к этому типу, независимо от того, реализован ли модуль на Python, C или на чем-то другом. Чтобы помочь организовать модули и обеспечить иерархию именования, в Python есть концепция packages.
Вы можете рассматривать пакеты как каталоги в файловой системе, а модули - как файлы внутри каталогов, но не стоит воспринимать эту аналогию слишком буквально, поскольку пакеты и модули не обязательно должны быть созданы в файловой системе. Для целей этой документации мы будем использовать эту удобную аналогию каталогов и файлов. Подобно каталогам файловой системы, пакеты организованы иерархически, и сами пакеты могут содержать подпакеты, а также обычные модули.
Важно помнить, что все пакеты являются модулями, но не все модули являются пакетами. Или, другими словами, пакеты - это просто особый вид модуля. В частности, любой модуль, содержащий атрибут __path__
, считается пакетом.
У всех модулей есть имя. Имена подпакетов отделяются от имени родительского пакета точкой, что соответствует стандартному синтаксису доступа к атрибутам в Python. Таким образом, у вас может быть пакет с именем email
, который, в свою очередь, имеет подпакет с именем email.mime
и модуль внутри этого подпакета с именем email.mime.text
.
5.2.1. Обычные посылки¶
Python определяет два типа пакетов: regular packages и namespace packages. Обычные пакеты - это традиционные пакеты в том виде, в каком они существовали в 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. Пакеты пространства имен¶
Пакет пространства имен представляет собой совокупность различных portions, где каждая часть вносит свой вклад в подпакет родительского пакета. Части могут находиться в разных местах файловой системы. Фрагменты также могут быть найдены в zip-файлах, в сети или в любом другом месте, которое Python ищет во время импорта. Пакеты пространства имен могут напрямую соответствовать объектам в файловой системе, а могут и не соответствовать; они могут быть виртуальными модулями, которые не имеют конкретного представления.
Пакеты пространства имен не используют обычный список для своего атрибута __path__
. Вместо этого они используют пользовательский итерируемый тип, который автоматически выполняет новый поиск частей пакета при следующей попытке импорта внутри этого пакета, если путь к их родительскому пакету (или sys.path
для пакета верхнего уровня) изменяется.
В случае с пакетами пространства имен файл parent/__init__.py
отсутствует. На самом деле, при поиске по импорту может быть найдено несколько каталогов parent
, каждый из которых представлен отдельной частью. Таким образом, parent/one
может физически не располагаться рядом с parent/two
. В этом случае Python будет создавать пакет пространства имен для пакета верхнего уровня parent
всякий раз, когда он или один из его подпакетов импортируется.
Смотрите также PEP 420 для получения информации о спецификации пакета пространства имен.
5.3. Поисковый¶
Чтобы начать поиск, Python нуждается в fully qualified имени импортируемого модуля (или пакета, но для целей данного обсуждения разница несущественна). Это имя может быть получено из различных аргументов оператора 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
.
Однако будьте осторожны, так как если вы сохраните ссылку на объект module, аннулируете его запись в кэше в sys.modules
, а затем повторно импортируете именованный модуль, два объекта module не будут одинаковыми. В отличие от этого, importlib.reload()
повторно использует тот же объект модуля и просто повторно инициализирует содержимое модуля, повторно запуская код модуля.
5.3.2. Поисковики и погрузчики¶
Если указанный модуль не найден в sys.modules
, то для поиска и загрузки модуля вызывается протокол импорта Python. Этот протокол состоит из двух концептуальных объектов, finders и loaders. Задача поисковика состоит в том, чтобы определить, может ли он найти указанный модуль, используя любую известную ему стратегию. Объекты, реализующие оба этих интерфейса, называются importers - они сами возвращаются, когда обнаруживают, что могут загрузить запрошенный модуль.
Python включает в себя ряд средств поиска и импорта по умолчанию. Первый из них знает, как найти встроенные модули, а второй - как найти замороженные модули. Третий инструмент поиска по умолчанию выполняет поиск модулей по import path. import path - это список местоположений, в котором могут быть указаны пути к файловой системе или zip-файлам. Его также можно расширить для поиска любых доступных ресурсов, например, тех, которые идентифицируются по URL-адресам.
Импортируемое оборудование является расширяемым, поэтому для расширения диапазона и объема поиска по модулям могут быть добавлены новые поисковики.
Поисковые системы на самом деле не загружают модули. Если они могут найти именованный модуль, они возвращают module spec, инкапсуляцию информации, связанной с импортом модуля, которую затем использует механизм импорта при загрузке модуля.
В следующих разделах более подробно описывается протокол для поисковых систем и загрузчиков, включая то, как вы можете создавать и регистрировать новые для расширения возможностей импортируемого оборудования.
Изменено в версии 3.4: В предыдущих версиях Python средства поиска возвращали loaders напрямую, тогда как теперь они возвращают спецификации модулей, которые содержат загрузчики. Загрузчики по-прежнему используются при импорте, но имеют меньше функций.
5.3.3. Импорт крючков¶
Механизм импорта разработан таким образом, чтобы быть расширяемым; основным механизмом для этого являются импортные крючки. Существует два типа импортных крючков: * металлические крючки* и * импортные крючки для траектории*.
Мета-перехватчики вызываются в начале обработки импорта, перед выполнением любой другой обработки импорта, кроме поиска в кэше sys.modules
. Это позволяет мета-перехватчикам переопределять обработку sys.path
, замороженные модули или даже встроенные модули. Мета-перехватчики регистрируются путем добавления новых объектов finder в sys.meta_path
, как описано ниже.
Перехватчики пути импорта вызываются как часть обработки sys.path
(или package.__path__
) в момент обнаружения связанного с ними элемента пути. Перехватчики пути импорта регистрируются путем добавления новых вызываемых объектов в sys.path_hooks
, как описано ниже.
5.3.4. Мета-путь¶
Когда именованный модуль не найден в sys.modules
, Python выполняет поиск в sys.meta_path
, который содержит список объектов поиска мета-путей. Эти средства поиска запрашиваются, чтобы узнать, знают ли они, как обращаться с именованным модулем. Средства поиска мета-путей должны реализовывать метод с именем find_spec()
, который принимает три аргумента: имя, путь импорта и (необязательно) целевой модуль. Средство поиска мета-путей может использовать любую стратегию, которую оно хочет, чтобы определить, может ли оно обрабатывать именованный модуль или нет.
Если средство поиска метапутей знает, как обращаться с именованным модулем, оно возвращает объект спецификации. Если оно не может справиться с именованным модулем, оно возвращает None
. Если обработка sys.meta_path
достигает конца своего списка, не возвращая спецификацию, то генерируется ModuleNotFoundError
. Любые другие возникающие исключения просто распространяются вверх, прерывая процесс импорта.
Метод поиска мета-путей find_spec()
вызывается с двумя или тремя аргументами. Первый - это полное имя импортируемого модуля, например foo.bar.baz
. Второй аргумент - это записи пути, которые будут использоваться для поиска модуля. Для модулей верхнего уровня вторым аргументом является None
, но для подмодулей или подпакетов вторым аргументом является значение атрибута родительского пакета __path__
. Если к соответствующему атрибуту __path__
получить доступ невозможно, генерируется ModuleNotFoundError
. Третий аргумент - это существующий объект модуля, который будет загружен позже. Система импорта переходит в целевой модуль только во время перезагрузки.
Метапуть может быть пройдена несколько раз для одного запроса на импорт. Например, если предположить, что ни один из задействованных модулей еще не был кэширован, при импорте foo.bar.baz
сначала будет выполнен импорт верхнего уровня с вызовом mpf.find_spec("foo", None, None)
в каждом методе поиска путей (mpf
). После того, как foo
был импортирован, foo.bar
будет импортирован путем повторного прохождения мета-пути с вызовом mpf.find_spec("foo.bar", foo.__path__, None)
. Как только foo.bar
будет импортирован, при окончательном обходе будет вызван mpf.find_spec("foo.bar.baz", foo.bar.__path__, None)
.
Некоторые средства поиска мета-путей поддерживают импорт только на верхнем уровне. Эти средства импорта всегда возвращают None
, когда в качестве второго аргумента передается что-либо, отличное от None
.
В Python по умолчанию sys.meta_path
есть три средства поиска мета-путей: одно, которое знает, как импортировать встроенные модули, другое, которое знает, как импортировать замороженные модули, и третье, которое знает, как импортировать модули из import path (т.е. из path based finder).
Изменено в версии 3.4: Метод find_spec()
для поиска метапутей заменил find_module()
, который теперь устарел. Хотя он будет продолжать работать без изменений, механизм импорта попробует его, только если поисковик не реализует find_spec()
.
Изменено в версии 3.10: При использовании find_module()
системой импорта теперь возникает ImportWarning
.
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)
# Set __loader__ and __package__ if missing.
else:
sys.modules[spec.name] = module
try:
spec.loader.exec_module(module)
except BaseException:
try:
del sys.modules[spec.name]
except KeyError:
pass
raise
return sys.modules[spec.name]
Обратите внимание на следующие детали:
Если существует объект модуля с указанным именем в
sys.modules
, импорт уже вернет его.Модуль будет существовать в
sys.modules
до того, как загрузчик выполнит код модуля. Это важно, потому что код модуля может (прямо или косвенно) импортировать сам себя; предварительное добавление его вsys.modules
предотвращает неограниченную рекурсию в худшем случае и многократную загрузку в лучшем.Если загрузка завершается неудачно, неисправный модуль - и только неисправный модуль - удаляется из
sys.modules
. Любой модуль, который уже находится в кэшеsys.modules
, и любой модуль, который был успешно загружен в качестве побочного эффекта, должны оставаться в кэше. Это контрастирует с перезагрузкой, при которой даже неисправный модуль остается вsys.modules
.После создания модуля, но перед выполнением, механизм импорта устанавливает атрибуты модуля, связанные с импортом («_init_module_attrs» в примере псевдокода выше), как указано в later section.
Выполнение модуля - это ключевой момент загрузки, при котором заполняется пространство имен модуля. Выполнение полностью делегируется загрузчику, который сам решает, что и как будет заполнено.
Модуль, созданный во время загрузки и переданный в 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()
, будет распространено.
Во многих случаях finder и loader могут быть одним и тем же объектом; в таких случаях метод find_spec()
просто возвращает спецификацию с параметром loader, равным self
.
Загрузчики модулей могут выбрать создание объекта module во время загрузки, реализовав метод create_module()
. Он принимает один аргумент, спецификацию модуля, и возвращает новый объект module для использования во время загрузки. create_module()
не требует установки каких-либо атрибутов для объекта module. Если метод возвращает None
, механизм импорта сам создаст новый модуль.
Добавлено в версии 3.4: Метод create_module()
загрузчиков.
Изменено в версии 3.4: Метод load_module()
был заменен на exec_module()
, и импортное оборудование взяло на себя все стандартные обязанности по загрузке.
Для обеспечения совместимости с существующими загрузчиками импортируемое оборудование будет использовать load_module()
метод загрузчиков, если он существует, и загрузчик также не реализует exec_module()
. Однако load_module()
устарел, и вместо него загрузчики должны реализовать exec_module()
.
Метод load_module()
должен реализовывать все стандартные функции загрузки, описанные выше, в дополнение к выполнению модуля. Применяются все те же ограничения, но с некоторыми дополнительными пояснениями:
Если существует объект 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__
объекта module. Более подробную информацию о содержании спецификации модуля смотрите в разделе ModuleSpec
.
Добавлено в версии 3.4.
5.4.5. модуль.__путь__¶
По определению, если модуль имеет атрибут __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. Повторное использование модуля¶
По умолчанию все модули имеют используемый repr, однако в зависимости от атрибутов, установленных выше, и в спецификации модуля вы можете более явно управлять repr объектов модуля.
Если в модуле есть спецификация, (__spec__
), механизм импорта попытается сгенерировать на ее основе отчет. Если это не удается или спецификации нет, система импорта создаст повторный отчет по умолчанию, используя любую информацию, доступную в модуле. Он попытается использовать module.__name__
, module.__file__
, и module.__loader__
в качестве входных данных в repr, используя значения по умолчанию для любой отсутствующей информации.
Вот точные используемые правила:
Если модуль имеет атрибут
__spec__
, то для генерации repr используется информация из спецификации. При этом учитываются атрибуты «name», «loader», «origin» и «has_location».Если модуль имеет атрибут
__file__
, он используется как часть представления модуля.Если в модуле нет
__file__
, но есть__loader__
, который не являетсяNone
, то repr загрузчика используется как часть repr модуля.В противном случае просто используйте значение модуля
__name__
в представлении.
Изменено в версии 3.4: Использование loader.module_repr()
устарело, и спецификация модуля теперь используется механизмом импорта для создания представления модуля.
Для обеспечения обратной совместимости с Python 3.3 модуль repr будет сгенерирован путем вызова метода загрузчика module_repr()
, если он определен, перед использованием любого из описанных выше подходов. Однако этот метод устарел.
Изменено в версии 3.10: Вызов module_repr()
теперь выполняется после попытки использовать атрибут модуля __spec__
, но до возврата к __file__
. Использование module_repr()
планируется прекратить в 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 поставляется с несколькими средствами поиска мета-путей по умолчанию. Один из них, называемый path based finder (PathFinder
), выполняет поиск в import path, который содержит список path entries. В каждой записи пути указано местоположение для поиска модулей.
Сам поисковик на основе путей не знает, как что-либо импортировать. Вместо этого он просматривает отдельные записи пути, связывая каждую из них с поисковиком записей пути, который знает, как обрабатывать этот конкретный тип пути.
Стандартный набор средств поиска путей реализует всю семантику поиска модулей в файловой системе, обрабатывая специальные типы файлов, такие как исходный код Python (.py
файлов), байт-код Python (.pyc
файлов) и общие библиотеки (например, .so
файлы). При поддержке модуля zipimport
в стандартной библиотеке средства поиска путей по умолчанию также обрабатывают загрузку всех этих типов файлов (кроме совместно используемых библиотек) из zipfiles.
Записи пути не обязательно должны быть ограничены расположениями в файловой системе. Они могут относиться к URL-адресам, запросам к базе данных или любому другому расположению, которое может быть указано в виде строки.
Программа поиска путей предоставляет дополнительные перехватчики и протоколы, позволяющие расширять и настраивать типы доступных для поиска записей путей. Например, если вы хотите поддерживать записи путей в виде сетевых URL-адресов, вы могли бы написать перехватчик, который реализует семантику HTTP для поиска модулей в Интернете. Этот хук (вызываемый) должен возвращать path entry finder, поддерживающий протокол, описанный ниже, который затем использовался для получения загрузчика для модуля из Интернета.
Хочу предупредить: в этом разделе, как и в предыдущем, используется термин «поиск», при этом между ними используются термины meta path finder и path entry finder. Эти два типа поисковиков очень похожи, поддерживают схожие протоколы и функционируют схожим образом в процессе импорта, но важно иметь в виду, что они немного отличаются друг от друга. В частности, средства поиска метапутей работают в начале процесса импорта, поскольку они отключены при обходе sys.meta_path
.
Напротив, средства поиска путей в некотором смысле являются деталью реализации средства поиска на основе путей, и фактически, если бы средство поиска на основе путей было удалено из sys.meta_path
, ни одна из семантик средства поиска путей не была бы вызвана.
5.5.1. Средства поиска начального пути¶
Параметр path based finder отвечает за поиск и загрузку модулей и пакетов Python, местоположение которых указано с помощью строки path entry. В большинстве записей пути указаны местоположения в файловой системе, но этим они не ограничиваются.
Как средство поиска мета-путей, path based finder реализует протокол find_spec()
, описанный ранее, однако он предоставляет дополнительные перехватчики, которые можно использовать для настройки способа поиска и загрузки модулей из import path.
В параметрах 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 - это meta path finder, поэтому механизм импорта начинает поиск import path, вызывая метод поиска на основе пути find_spec()
, как описано ранее. Когда задан аргумент path
для find_spec()
, это будет список строковых путей для обхода - обычно это атрибут __path__
пакета для импорта внутри этого пакета. Если аргументом path
является None
, это указывает на импорт верхнего уровня и используется sys.path
.
Поисковик на основе пути выполняет итерацию по каждой записи в пути поиска и для каждой из них ищет подходящий path entry finder (PathEntryFinder
) для ввода пути. Поскольку это может быть дорогостоящей операцией (например, для этого поиска могут потребоваться stat()
дополнительных вызова), поисковик на основе путей поддерживает кэширование, сопоставляющее записи о путях со средствами поиска записей о путях. Этот кэш хранится в sys.path_importer_cache
(несмотря на название, на самом деле в этом кэше хранятся объекты finder, а не только объекты importer). Таким образом, дорогостоящий поиск определенного path entry местоположения path entry finder нужно выполнить только один раз. Пользовательский код может свободно удалять записи кэша из sys.path_importer_cache
, заставляя программу поиска на основе пути повторно выполнять поиск по записи пути [3].
Если запись пути отсутствует в кэше, программа поиска на основе пути выполняет итерацию по каждому вызываемому объекту в sys.path_hooks
. Каждый из path entry hooks в этом списке вызывается с единственным аргументом - записью пути, которую необходимо найти. Этот вызываемый объект может либо возвращать path entry finder, который может обрабатывать запись пути, либо вызывать ImportError
. ImportError
используется поисковиком на основе пути, чтобы сигнализировать о том, что перехватчику не удается найти path entry finder для этого path entry. Исключение игнорируется, и import path итерация продолжается. Перехватчик должен ожидать либо объект string, либо объект bytes; кодировка объектов bytes зависит от перехватчика (например это может быть кодировка файловой системы, UTF-8 или что-то еще), и если перехватчик не может расшифровать аргумент, он должен вызвать ImportError
.
Если sys.path_hooks
итерация завершается без возврата path entry finder, то метод поиска на основе пути find_spec()
сохранит None
в sys.path_importer_cache
(чтобы указать, что нет никакого finder для этой записи пути) и возвращает None
, указывая, что этому meta path finder модулю не удалось найти.
Если path entry finder * возвращается одним из path entry hook вызываемых объектов в sys.path_hooks
, то используется следующий протокол, чтобы запросить у finder спецификацию модуля, которая затем используется при загрузке модуля.
Текущий рабочий каталог, обозначаемый пустой строкой, обрабатывается несколько иначе, чем другие записи в sys.path
. Во-первых, если обнаружено, что текущий рабочий каталог не существует, в sys.path_importer_cache
не сохраняется значение. Во-вторых, значение для текущего рабочего каталога обновляется при каждом обращении к модулю. В-третьих, путь, используемый для sys.path_importer_cache
и возвращаемый importlib.machinery.PathFinder.find_spec()
, будет фактическим текущим рабочим каталогом, а не пустой строкой.
5.5.2. Протокол поиска записи пути¶
Чтобы поддерживать импорт модулей и инициализированных пакетов, а также добавлять части в пакеты пространства имен, средства поиска путей должны реализовывать метод find_spec()
.
find_spec()
принимает два аргумента: полное имя импортируемого модуля и (необязательный) целевой модуль. find_spec()
возвращает полностью заполненную спецификацию для модуля. Для этой спецификации всегда будет установлен параметр «loader» (за одним исключением).
Чтобы указать механизму импорта, что спецификация представляет собой пространство имен portion, средство поиска пути устанавливает «submodule_search_locations» в список, содержащий эту часть.
Изменено в версии 3.4: find_spec()
заменены find_loader()
и find_module()
, оба из которых теперь устарели, но будут использоваться, если find_spec()
не определено.
Более старые средства поиска путей могут использовать один из этих двух устаревших методов вместо find_spec()
. Эти методы по-прежнему соблюдаются в целях обеспечения обратной совместимости. Однако, если в средстве поиска путей реализован find_spec()
, устаревшие методы игнорируются.
find_loader()
принимает один аргумент - полное имя импортируемого модуля. find_loader()
возвращает кортеж из 2 элементов, где первый элемент - это загрузчик, а второй элемент - пространство имен 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. Замена стандартной системы импорта¶
Самый надежный механизм для замены всей системы импорта - это удалить содержимое sys.meta_path
по умолчанию, полностью заменив его пользовательским перехватом мета-пути.
Если допустимо изменять только поведение инструкций import, не затрагивая другие API, которые обращаются к системе импорта, то может быть достаточно заменить встроенную функцию __import__()
. Этот метод также может быть использован на уровне модуля только для изменения поведения инструкций 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
как полезное выражение, но .module не является допустимым выражением.
5.8. Особые соображения по __основному__¶
Модуль __main__
является особым случаем по отношению к системе импорта Python. Как отмечалось elsewhere, модуль __main__
инициализируется непосредственно при запуске интерпретатора, подобно sys
и builtins
. Однако, в отличие от этих двух, он не является встроенным модулем. Это связано с тем, что способ инициализации __main__
зависит от флагов и других параметров, с помощью которых вызывается интерпретатор.
5.8.1. __основная__.__спецификация__¶
В зависимости от того, как инициализируется __main__
, __main__.__spec__
устанавливается соответствующим образом или в None
.
Когда Python запускается с параметром -m
, __spec__
устанавливается в качестве спецификации модуля соответствующего модуля или пакета. __spec__
также заполняется, когда модуль __main__
загружается как часть запуск каталога, zip-файла или другой записи sys.path
.
В the remaining cases __main__.__spec__
задано значение None
, поскольку код, используемый для заполнения __main__
, напрямую не соответствует импортируемому модулю:
интерактивная подсказка
-c
опциязапуск из стандартного кода
запуск непосредственно из исходного кода или файла байт-кода
Обратите внимание, что __main__.__spec__
всегда равно None
в последнем случае, даже если технически файл можно было бы импортировать напрямую как модуль. Используйте переключатель -m
, если в __main__
требуются допустимые метаданные модуля.
Также обратите внимание, что даже если __main__
соответствует импортируемому модулю и __main__.__spec__
установлено соответствующим образом, они все равно считаются разными модулями. Это связано с тем, что блоки, защищенные проверками if __name__ == "__main__":
, выполняются только тогда, когда модуль используется для заполнения пространства имен __main__
, а не во время обычного импорта.
5.9. Рекомендации¶
Механизм импорта значительно изменился с момента появления Python. Оригинал specification for packages по-прежнему доступен для чтения, хотя некоторые детали изменились с момента написания этого документа.
Первоначальная спецификация для sys.meta_path
была PEP 302, с последующим расширением в PEP 420.
PEP 420 введен namespace packages для Python 3.3. PEP 420 также введен протокол find_loader()
в качестве альтернативы find_module()
.
PEP 366 описывает добавление атрибута __package__
для явного относительного импорта в основных модулях.
PEP 328 ввел абсолютный и явный относительный импорт и первоначально предлагал __name__
для семантики PEP 366, которая в конечном итоге будет указана для __package__
.
PEP 338 определяет исполняемые модули как скрипты.
PEP 451 добавлена возможность инкапсуляции состояния импорта для каждого модуля в объекты спецификации. Это также переносит большинство стандартных обязанностей загрузчиков обратно на механизм импорта. Эти изменения позволяют отказаться от использования нескольких API в системе импорта, а также добавить новые методы в средства поиска и загрузки.
Сноски