Spec-Zone.ru › Python 3.12

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

Код Python в одном модуле получает доступ к коду другого модуля посредством его импорта. Инструкция import — наиболее распространённый способ обращения к механизму импорта, но не единственный. Для обращения к механизму импорта также можно использовать такие функции, как importlib.import_module(), и встроенную функцию __import__().

Инструкция import объединяет две операции: поиск указанного модуля, а затем привязку результатов поиска к имени в локальной области видимости. Поиск, выполняемый инструкцией import, определяется как вызов функции __import__() с соответствующими аргументами. Возвращаемое значение __import__() используется для выполнения операции привязки имени, предусмотренной инструкцией import. Точные подробности этой операции привязки имени см. в описании инструкции import.

Прямой вызов __import__() выполняет только поиск модуля и, если он найден, создание модуля. При этом могут возникать некоторые побочные эффекты, например импорт родительских пакетов и обновление различных кэшей (в том числе sys.modules), однако только инструкция import выполняет операцию привязки имени.

При выполнении инструкции import вызывается стандартная встроенная функция __import__(). Другие механизмы обращения к системе импорта (например, importlib.import_module()) могут обходить __import__() и использовать собственные решения для реализации семантики импорта.

При первом импорте модуля Python выполняет его поиск и, если модуль найден, создаёт объект модуля [1] и инициализирует его. Если указанный модуль не удаётся найти, возникает исключение ModuleNotFoundError. При обращении к механизму импорта Python использует различные стратегии поиска указанного модуля. Эти стратегии можно изменять и расширять с помощью различных перехватчиков, описанных в разделах ниже.

Изменено в версии 3.3: Система импорта была обновлена для полной реализации второй фазы PEP 302. Неявного механизма импорта больше нет — вся система импорта доступна через sys.meta_path. Кроме того, реализована встроенная поддержка пакетов пространств имён (см. PEP 420).

5.1. importlib

Модуль importlib предоставляет богатый API для взаимодействия с системой импорта. Например, importlib.import_module() предоставляет рекомендуемый, более простой API для обращения к механизму импорта, чем встроенная функция __import__(). Дополнительные сведения см. в документации библиотеки importlib.

5.2. Пакеты

В Python есть только один тип объектов модулей, и все модули относятся к этому типу, независимо от того, реализованы ли они на Python, C или другом языке. Для упорядочивания модулей и создания иерархии имён в Python существует понятие пакетов.

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

Важно помнить, что все пакеты являются модулями, но не все модули являются пакетами. Иными словами, пакеты — это особый вид модулей. В частности, любой модуль, содержащий атрибут __path__, считается пакетом.

У всех модулей есть имя. Имена вложенных пакетов отделяются от имени родительского пакета точкой, как и в стандартном синтаксисе доступа к атрибутам Python. Так, у вас может быть пакет email, содержащий вложенный пакет email.mime, в котором находится модуль email.mime.text.

5.2.1. Обычные пакеты

Python определяет два типа пакетов: обычные пакеты и пакеты пространств имён. Обычные пакеты — это традиционные пакеты, существовавшие в Python 3.2 и более ранних версиях. Обычный пакет обычно реализован как каталог, содержащий файл __init__.py. При импорте обычного пакета этот файл __init__.py неявно выполняется, а определённые в нём объекты связываются с именами в пространстве имён пакета. Файл __init__.py может содержать тот же код Python, что и любой другой модуль, а при импорте Python добавит в модуль некоторые дополнительные атрибуты.

Например, следующая структура файловой системы задаёт пакет верхнего уровня parent с тремя вложенными пакетами:

parent/
    __init__.py
    one/
        __init__.py
    two/
        __init__.py
    three/
        __init__.py

Импорт parent.one неявно выполнит parent/__init__.py и parent/one/__init__.py. При последующем импорте parent.two или parent.three будут выполнены соответственно parent/two/__init__.py и parent/three/__init__.py.

5.2.2. Пакеты пространств имён

Пакет пространства имён — это объединение различных частей, каждая из которых добавляет вложенный пакет в родительский пакет. Части могут находиться в разных местах файловой системы. Их также можно найти в ZIP-файлах, в сети или в любом другом месте, где Python выполняет поиск при импорте. Пакеты пространств имён могут как соответствовать объектам в файловой системе, так и не соответствовать им; они могут быть виртуальными модулями, не имеющими конкретного представления.

Для атрибута __path__ пакеты пространств имён не используют обычный список. Вместо этого они используют специальный итерируемый тип, который автоматически выполняет новый поиск частей пакета при следующей попытке импорта внутри этого пакета, если путь родительского пакета (или sys.path для пакета верхнего уровня) изменился.

У пакетов пространств имён нет файла parent/__init__.py. Более того, во время поиска при импорте могут быть найдены несколько каталогов parent, каждый из которых предоставлен отдельной частью. Поэтому parent/one может физически находиться не рядом с parent/two. В этом случае Python создаст пакет пространства имён для пакета верхнего уровня parent при его импорте или импорте одного из его вложенных пакетов.

Спецификацию пакетов пространств имён см. также в PEP 420.

5.3. Поиск

Для начала поиска Python необходимо полное имя импортируемого модуля (или пакета, но для целей этого обсуждения различие несущественно). Это имя может быть получено из различных аргументов инструкции import или из параметров функций importlib.import_module() либо __import__().

Это имя будет использоваться на разных этапах поиска при импорте и может представлять собой путь с точками к вложенному модулю, например foo.bar.baz. В этом случае Python сначала пытается импортировать foo, затем foo.bar и наконец foo.bar.baz. Если импорт любого из промежуточных модулей завершится неудачно, возникнет исключение ModuleNotFoundError.

5.3.1. Кэш модулей

Первое место, проверяемое при поиске во время импорта, — это sys.modules. Эта структура служит кэшем всех ранее импортированных модулей, включая промежуточные пути. Поэтому, если foo.bar.baz уже импортирован, sys.modules будет содержать записи для foo, foo.bar и foo.bar.baz. Значением каждого ключа будет соответствующий объект модуля.

Во время импорта имя модуля ищется в sys.modules. Если оно присутствует, связанное с ним значение — это модуль, удовлетворяющий запросу на импорт, и процесс завершается. Однако если значение равно None, возникает исключение ModuleNotFoundError. Если имя модуля отсутствует, Python продолжает его поиск.

Содержимое sys.modules можно изменять. Удаление ключа может не уничтожить соответствующий модуль (поскольку на него могут ссылаться другие модули), но аннулирует запись в кэше для указанного модуля, в результате чего при следующем импорте Python выполнит его новый поиск. Ключу также можно присвоить значение None, чтобы при следующем импорте модуля возникло исключение ModuleNotFoundError.

Однако будьте осторожны: если вы сохраните ссылку на объект модуля, аннулируете его запись в кэше sys.modules, а затем повторно импортируете указанный модуль, два объекта модуля не будут одинаковыми. Напротив, importlib.reload() повторно использует тот же объект модуля и просто заново инициализирует его содержимое, повторно выполняя код модуля.

5.3.2. Поисковые механизмы и загрузчики

Если указанный модуль не найден в sys.modules, Python обращается к протоколу импорта для поиска и загрузки модуля. Этот протокол состоит из двух концептуальных объектов: поисковых механизмов и загрузчиков. Задача поискового механизма — определить, может ли он найти указанный модуль с помощью известной ему стратегии. Объекты, реализующие оба этих интерфейса, называются импортерами: обнаружив, что они могут загрузить запрошенный модуль, они возвращают сами себя.

В Python предусмотрено несколько поисковых механизмов и импортеров по умолчанию. Первый умеет находить встроенные модули, а второй — замороженные модули. Третий поисковый механизм по умолчанию ищет модули в пути импорта. Путь импорта — это список мест, которые могут указывать на пути файловой системы или ZIP-файлы. Его также можно расширить для поиска любых обнаруживаемых ресурсов, например тех, что доступны по URL-адресам.

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

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

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

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

5.3.3. Перехватчики импорта

Механизм импорта спроектирован с возможностью расширения; основной способ сделать это — использовать перехватчики импорта. Существует два типа перехватчиков импорта: метаперехватчики и перехватчики пути импорта.

Метаперехватчики вызываются в начале обработки импорта, до выполнения любой другой обработки импорта, кроме поиска в кэше sys.modules. Это позволяет метаперехватчикам переопределять обработку sys.path, замороженных модулей и даже встроенных модулей. Метаперехватчики регистрируются добавлением новых объектов поисковых механизмов в sys.meta_path, как описано ниже.

Перехватчики пути импорта вызываются в ходе обработки sys.path (или package.__path__), когда обнаруживается связанный с ними элемент пути. Перехватчики пути импорта регистрируются добавлением новых вызываемых объектов в sys.path_hooks, как описано ниже.

5.3.4. Метапуть

Если указанный модуль не найден в sys.modules, Python выполняет поиск в sys.meta_path, содержащем список поисковых механизмов метапути. Эти механизмы по очереди проверяются на предмет того, умеют ли они обрабатывать указанный модуль. Поисковые механизмы метапути должны реализовывать метод find_spec(), принимающий три аргумента: имя, путь импорта и (необязательный) целевой модуль. Поисковый механизм метапути может использовать любую стратегию, чтобы определить, может ли он обработать указанный модуль.

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

Метод find_spec() поисковых механизмов метапути вызывается с двумя или тремя аргументами. Первый аргумент — полное имя импортируемого модуля, например foo.bar.baz. Второй аргумент — элементы пути, используемые для поиска модуля. Для модулей верхнего уровня второй аргумент равен None, а для вложенных модулей или пакетов — значению атрибута __path__ родительского пакета. Если не удаётся получить доступ к соответствующему атрибуту __path__, возникает исключение ModuleNotFoundError. Третий аргумент — существующий объект модуля, который впоследствии станет целевым объектом загрузки. Система импорта передаёт целевой модуль только при перезагрузке.

В ходе одного запроса на импорт метапуть может проходиться несколько раз. Например, если ни один из задействованных модулей ещё не был помещён в кэш, импорт foo.bar.baz сначала выполнит импорт модуля верхнего уровня, вызывая mpf.find_spec("foo", None, None) для каждого поискового механизма метапути (mpf). После импорта foo модуль foo.bar будет импортирован при повторном проходе по метапути, который вызовет mpf.find_spec("foo.bar", foo.__path__, None). После импорта foo.bar последний проход вызовет mpf.find_spec("foo.bar.baz", foo.bar.__path__, None).

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

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

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

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

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

5.4. Загрузка

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

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

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

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

  • Если объект модуля с указанным именем уже существует в sys.modules, импорт уже вернул бы его.
  • Модуль появится в sys.modules до того, как загрузчик выполнит код модуля. Это крайне важно, поскольку код модуля может (прямо или косвенно) импортировать сам себя; предварительное добавление модуля в sys.modules предотвращает бесконечную рекурсию в худшем случае и повторную загрузку в лучшем.
  • Если загрузка завершается с ошибкой, из sys.modules удаляется модуль, вызвавший ошибку, — и только он. Любые модули, уже находящиеся в кэше sys.modules, а также все модули, успешно загруженные в качестве побочного эффекта, должны остаться в кэше. Это отличается от повторной загрузки, при которой даже модуль, вызвавший ошибку, остаётся в sys.modules.
  • После создания модуля, но до его выполнения импортирующая система задаёт атрибуты модуля, связанные с импортом («_init_module_attrs» в приведённом выше примере псевдокода), как описано в следующем разделе.
  • Выполнение модуля — ключевой этап загрузки, на котором заполняется пространство имён модуля. Выполнение полностью делегировано загрузчику, который сам решает, что и как заполнять.
  • Модуль, созданный во время загрузки и переданный в exec_module(), может отличаться от того, который возвращается в конце импорта [2].

Изменено в версии 3.4: Импортирующая система взяла на себя стандартные обязанности загрузчиков. Ранее они выполнялись методом importlib.abc.Loader.load_module().

5.4.1. Загрузчики

Загрузчики модулей обеспечивают важнейшую функцию загрузки: выполнение модуля. Импортирующая система вызывает метод importlib.abc.Loader.exec_module() с одним аргументом — объектом модуля, который нужно выполнить. Возвращаемое значение exec_module() игнорируется.

Загрузчики должны соответствовать следующим требованиям:

  • Если модуль является модулем Python (а не встроенным модулем или динамически загружаемым расширением), загрузчик должен выполнить код модуля в глобальном пространстве имён модуля (module.__dict__).
  • Если загрузчик не может выполнить модуль, он должен вызвать исключение ImportError, однако любое другое исключение, возникшее во время exec_module(), будет передано дальше.

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

Загрузчики модулей могут создавать объект модуля во время загрузки, реализовав метод create_module(). Он принимает один аргумент — спецификацию модуля — и возвращает новый объект модуля, который будет использоваться во время загрузки. create_module() не обязан задавать какие-либо атрибуты объекта модуля. Если метод возвращает None, импортирующая система создаёт новый модуль самостоятельно.

Добавлено в версии 3.4: Метод create_module() загрузчиков.

Изменено в версии 3.4: Метод load_module() был заменён на exec_module(), а импортирующая система взяла на себя все стандартные обязанности по загрузке.

Для совместимости с существующими загрузчиками импортирующая система будет использовать метод load_module() загрузчиков, если он существует, а загрузчик не реализует также exec_module(). Однако load_module() объявлен устаревшим, и загрузчикам следует реализовывать вместо него exec_module().

Метод load_module() должен реализовывать все описанные выше стандартные функции загрузки, а также выполнять модуль. Действуют те же ограничения, с некоторыми дополнительными уточнениями:

  • Если объект модуля с указанным именем уже существует в sys.modules, загрузчик должен использовать этот существующий модуль. (Иначе importlib.reload() не будет работать правильно.) Если именованного модуля нет в sys.modules, загрузчик должен создать новый объект модуля и добавить его в sys.modules.
  • Модуль должен находиться в sys.modules до того, как загрузчик выполнит код модуля, чтобы предотвратить бесконечную рекурсию или повторную загрузку.
  • Если загрузка завершается с ошибкой, загрузчик должен удалить все модули, которые он добавил в sys.modules, но он должен удалить только модули, вызвавшие ошибку, и только если загрузчик сам явно загрузил эти модули.

Изменено в версии 3.5: Вызывается предупреждение DeprecationWarning, если exec_module() определён, а create_module() — нет.

Изменено в версии 3.6: Вызывается исключение ImportError, если exec_module() определён, а create_module() — нет.

Изменено в версии 3.10: Использование load_module() вызывает предупреждение ImportWarning.

5.4.2. Подмодули

При загрузке подмодуля любым способом (например, с помощью 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__ применяется и к модулям, инициализированным при запуске интерпретатора. Единственное исключение — __main__, для которого __spec__ в некоторых случаях устанавливается в None.

Подробнее о содержимом спецификации модуля см. в разделе ModuleSpec.

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

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

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

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

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

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

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

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

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

Ниже приведены точные правила:

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

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

5.4.6. Инвалидация кэшированного байт-кода

Перед загрузкой кэшированного байт-кода из файла .pyc Python проверяет, актуален ли кэш относительно исходного файла .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. Поисковики записей пути

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

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

Поисковик на основе пути использует три переменные: sys.path, sys.path_hooks и sys.path_importer_cache. Также используются атрибуты __path__ объектов пакетов. Они предоставляют дополнительные возможности для настройки механизма импорта.

sys.path содержит список строк, задающих расположения для поиска модулей и пакетов. Он инициализируется из переменной окружения PYTHONPATH и различных других значений по умолчанию, зависящих от установки и реализации. Элементы sys.path могут указывать на каталоги в файловой системе, ZIP-файлы и, возможно, другие «расположения» (см. модуль site), в которых следует искать модули, например URL-адреса или запросы к базам данных. В sys.path должны находиться только строки; значения всех остальных типов игнорируются.

Поисковик на основе пути является поисковиком метапути, поэтому механизм импорта начинает поиск по пути импорта, вызывая метод find_spec() поисковика на основе пути, как описано ранее. Если аргумент path метода find_spec() задан, он будет представлять собой список строковых путей для обхода — обычно атрибут __path__ пакета при импорте внутри этого пакета. Если аргумент path равен None, это указывает на импорт верхнего уровня, и используется sys.path.

Поисковик на основе пути перебирает каждую запись в пути поиска и для каждой из них ищет подходящий поисковик записи пути (PathEntryFinder). Поскольку эта операция может быть затратной (например, поиск может сопровождаться накладными расходами на вызов stat()), поисковик на основе пути поддерживает кэш, сопоставляющий записи пути с поисковиками записей пути. Этот кэш хранится в sys.path_importer_cache (несмотря на название, в этом кэше хранятся объекты-поисковики, а не только объекты импортёров). Благодаря этому затратный поиск поисковика записи пути для заданного расположения записи пути нужно выполнять только один раз. Пользовательский код может удалять записи из кэша sys.path_importer_cache, заставляя поисковик на основе пути повторно выполнять поиск по записи пути.

Если записи пути нет в кэше, поисковик на основе пути перебирает каждый вызываемый объект в sys.path_hooks. Каждый хук записи пути из этого списка вызывается с одним аргументом — записью пути, которую нужно проверить. Этот вызываемый объект может либо вернуть поисковик записи пути, способный обработать запись пути, либо вызвать исключение ImportError. Поисковик на основе пути использует ImportError, чтобы сообщить, что хук не может найти поисковик записи пути для этой записи пути. Исключение игнорируется, и обход пути импорта продолжается. Хук должен ожидать строковый объект или объект bytes; кодировка объектов bytes определяется хуком (например, это может быть кодировка файловой системы, 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() возвращает полностью заполненную спецификацию модуля. В этой спецификации всегда задано значение «loader» (за одним исключением).

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

Изменено в версии 3.4: Метод find_spec() заменил методы find_loader() и find_module(). Оба метода теперь устарели, но будут использоваться, если find_spec() не определён.

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

Метод find_loader() принимает один аргумент — полное имя импортируемого модуля. find_loader() возвращает кортеж из двух элементов: первый элемент — загрузчик, а второй — часть пространства имён.

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

Метод find_module() поисковиков записей пути устарел, поскольку не позволяет поисковику записи пути добавлять части в пакеты пространств имён. Если в поисковике записи пути присутствуют и find_loader(), и find_module(), система импорта всегда будет вызывать find_loader(), а не find_module().

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

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

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

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

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

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

5.7. Относительные импорты пакетов

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

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

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

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

В абсолютных импортах можно использовать синтаксис import <> или from <> import <>, но в относительных импортах допустима только вторая форма. Причина в том, что:

import XXX.YYY.ZZZ

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

5.8. Особенности __main__

Модуль __main__ занимает особое место в системе импорта Python. Как отмечалось в другом месте, модуль __main__ инициализируется непосредственно при запуске интерпретатора, подобно sys и builtins. Однако, в отличие от этих двух модулей, он не является строго встроенным модулем. Это связано с тем, что способ инициализации __main__ зависит от флагов и других параметров, с которыми запускается интерпретатор.

5.8.1. __main__.__spec__

В зависимости от способа инициализации __main__, __main__.__spec__ получает соответствующее значение или значение None.

Если Python запускается с параметром -m, __spec__ получает спецификацию соответствующего модуля или пакета. __spec__ также заполняется, если модуль __main__ загружается при выполнении каталога, ZIP-файла или другой записи sys.path.

Во всех остальных случаях __main__.__spec__ получает значение None, поскольку код, используемый для заполнения __main__, напрямую не соответствует импортируемому модулю:

  • интерактивная оболочка
  • -c
  • запуск из стандартного ввода
  • непосредственный запуск исходного файла или файла байт-кода

Обратите внимание: в последнем случае __main__.__spec__ всегда равно None, даже если файл технически можно импортировать напрямую как модуль. Если в __main__ нужны корректные метаданные модуля, используйте параметр -m.

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

Сноски

[1]

См. types.ModuleType.

[2]

Реализация importlib не использует возвращаемое значение напрямую. Вместо этого она получает объект модуля, находя имя модуля в sys.modules. Это приводит к тому, что импортированный модуль может заменить себя в sys.modules. Такое поведение зависит от реализации и не гарантируется в других реализациях Python.

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

Spec-Zone.ru

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