Spec-Zone.ru › Python 3.10

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

Код 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.

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» в примере псевдокода выше), как суммировано в позже раздела.
  • Выполнение модуля — ключевой момент загрузки, в котором пространство имён модуля заполняется. Выполнение полностью делегировано загрузчику, который решает, что и как заполнять.
  • Модуль, созданный во время загрузки и переданный exec_module(), может не быть тем, который возвращается в конце импорта 2.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

5.4.2. Подмодули

При загрузке подмодуля любым способом (например, через importlib API, операторы import или import-from, или встроенные __import__()) в пространстве имён родительского модуля создаётся ссылка на объект подмодуля. Например, если пакет spam содержит подмодуль foo, после импорта spam.foo, у spam будет атрибут foo, который связан с подмодулем. Предположим, что у вас есть следующая структура каталогов:

spam/
    __init__.py
    foo.py

и в файле spam/__init__.py содержится следующая строка:

from .foo import Foo

тогда выполнение следующего кода создаст ссылки на имена foo и Foo в модуле spam:

>>> import spam
>>> spam.foo
<module 'spam.foo' from '/tmp/imports/spam/foo.py'>
>>> spam.Foo
<class 'spam.foo.Foo'>

Учитывая знакомые правила связывания имён в Python, это может показаться удивительным, но это на самом деле фундаментальная особенность системы импорта. Сохраняется инвариант: если у вас есть sys.modules['spam'] и sys.modules['spam.foo'] (как после указанного выше импорта), последняя должна появиться как атрибут foo первой.

5.4.3. Спецификация модуля

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

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

Спецификация модуля доступна в качестве атрибута __spec__ объекта модуля. Подробнее о содержимом спецификации модуля см. в ModuleSpec.

Новое в версии 3.4.

5.4.4. Атрибуты модулей, связанные с импортом

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

__name__

Атрибут __name__ должен быть установлен в полное квалифицированное имя модуля. Это имя используется для уникальной идентификации модуля в системе импорта.

__loader__

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

__package__

Атрибут __package__ модуля должен быть установлен. Его значение должно быть строкой, но оно может быть таким же, как его значение __name__. Когда модуль является пакетом, его значение __package__ должно быть установлено в его значение __name__. Когда модуль не является пакетом, __package__ должен быть установлен в пустую строку для модулей верхнего уровня или для подмодулей, в имя родительского пакета. См. PEP 366 для получения дополнительных подробностей.

Этот атрибут используется вместо __name__ для расчета явных относительных импортов для основных модулей, как определено в PEP 366. Ожидается, что он будет иметь то же значение, что и __spec__.parent.

Изменено в версии 3.6: Ожидается, что значение __package__ будет таким же, как и значение __spec__.parent.

__spec__

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

Когда __package__ не определено, используется __spec__.parent в качестве резервного варианта.

Введено в версии 3.4.

Изменено в версии 3.6: __spec__.parent используется в качестве резервного варианта, когда __package__ не определено.

__path__

Если модуль является пакетом (обычным или пространством имён), атрибут __path__ объекта модуля должен быть установлен. Значение должно быть итерируемым, но может быть пустым, если __path__ не имеет дальнейшего значения. Если __path__ не пусто, при итерировании оно должно генерировать строки. Более подробные сведения о семантике __path__ приведены ниже.

У модулей, которые не являются пакетами, атрибут __path__ не должен присутствовать.

__file__
__cached__

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

Если __file__ установлен, может быть уместно установить атрибут __cached__, который является путем к любой скомпилированной версии кода (например, файлу с байткодом). Файл не обязательно должен существовать для установки этого атрибута; путь может просто указывать на место, где скомпилированный файл должен находиться (см. PEP 3147).

Также уместно установить __cached__, когда __file__ не установлено. Однако такой сценарий довольно необычен. В конечном итоге, загрузчик использует __file__ и/или __cached__. Таким образом, если загрузчик может загружать из кэшированного модуля, но иначе не загружает из файла, такой необычный сценарий может быть уместен.

5.4.5. module.__path__

По определению, если у модуля есть атрибут __path__, он является пакетом.

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

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

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

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

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

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

Вот точные правила:

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

Изменено в версии 3.4: Использование loader.module_repr() устарело, и теперь система импорта использует спецификацию модуля для генерации представления модуля.

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

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

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

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

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

Изменено в версии 3.7: Добавлены файлы кэша с использованием хешей. Ранее Python поддерживал только проверку кэшей байткода на основе отметки времени.

5.5. Поиск по пути

Как упоминалось ранее, Python поставляется с несколькими встроенными мета-поисковиками. Один из них, называемый поисковиком по пути (PathFinder), ищет в пути импорта, который содержит список элементов пути. Каждый элемент пути указывает место, где следует искать модули.

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

Набор стандартных поисковиков элементов пути реализует всю семантику поиска модулей в файловой системе, обрабатывая специальные типы файлов, такие как исходный код Python (.py файлы), байткод Python (.pyc файлы) и общие библиотеки (например, .so файлы). При поддержке модуля zipimport стандартной библиотеки, стандартные поисковики элементов пути также обрабатывают загрузку всех этих типов файлов (кроме общих библиотек) из архивов zip.

Элементы пути необязательно должны быть ограничены расположением в файловой системе. Они могут ссылаться на URL-адреса, запросы к базам данных или любое другое местоположение, которое может быть указано в виде строки.

Поисковик по пути предоставляет дополнительные крючки и протоколы, позволяющие расширять и настраивать типы элементов пути, которые можно искать. Например, если вы хотели бы поддерживать элементы пути в качестве сетевых URL-адресов, вы могли бы написать крючок, реализующий семантику HTTP для поиска модулей в интернете. Этот крючок (вызываемый объект) возвращает поисковик элементов пути, поддерживающий описанный ниже протокол, который затем использовался для получения загрузчика модуля из сети.

Предупреждение: эта и предыдущая секции используют термин поисковик, различая их с помощью терминов мета-поисковик и поисковик элементов пути. Эти два типа поисковиков очень похожи, поддерживают похожие протоколы и работают аналогичным образом во время процесса импорта, но важно помнить, что они немного отличаются. В частности, мета-поисковики работают в начале процесса импорта, как ключи перебора sys.meta_path.

В противоположность этому, поисковики элементов пути, в некотором смысле, являются деталями реализации поисковика по пути, и если бы поисковик по пути был удален из sys.meta_path, никакие семантики поисковиков элементов пути не были бы вызваны.

5.5.1. Поиск по элементам пути

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

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

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

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

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

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

Если элемент пути отсутствует в кеше, поисковик по пути итерируется по всем вызовам в sys.path_hooks. Каждый из крючков элементов пути в этом списке вызывается с одним аргументом — элементом пути, который нужно искать. Этот вызов либо возвращает поисковик элемента пути, который может обработать элемент пути, либо может вызвать ImportError. ImportError используется поисковиком по пути для сигнализации о том, что крючок не может найти поисковик элемента пути для этого элемента пути. Исключение игнорируется, и итерация по пути импорта продолжается. Крючок должен ожидать строку или байтовый объект; кодировка байтовых объектов зависит от крючка (например, это может быть кодировка файловой системы, UTF-8 или что-то другое), и если крючок не может декодировать аргумент, он должен вызвать ImportError.

Если итерация по sys.path_hooks завершается без возвращения поисковика элемента пути, то метод find_spec() поисковика по пути сохранит None в sys.path_importer_cache (чтобы указать, что для этого элемента пути нет поисковика) и вернет None, указывая, что этот мета-поисковик не смог найти модуль.

Если поисковик элемента пути возвращается одним из вызовов крючка элемента пути в sys.path_hooks, то для запроса у поисковика спецификации модуля используется следующий протокол, а затем эта спецификация используется при загрузке модуля.

Текущая рабочая директория — обозначаемая пустой строкой — обрабатывается немного иначе, чем другие элементы в sys.path. Во-первых, если текущая рабочая директория не найдена, значение не сохраняется в sys.path_importer_cache. Во-вторых, значение для текущей рабочей директории обновляется для каждого поиска модуля. В-третьих, путь, используемый для sys.path_importer_cache и возвращаемый методом importlib.machinery.PathFinder.find_spec(), будет фактической текущей рабочей директорией, а не пустой строкой.

5.5.2. Протокол поиска элементов пути

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

find_spec() принимает два аргумента: полное имя импортируемого модуля и (необязательный) целевой модуль. find_spec() возвращает полностью заполненный объект спецификации для модуля. Эта спецификация всегда будет иметь установленный «загрузчик» (за исключением одного случая).

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

import XXX.YYY.ZZZ

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

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

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

5.8.1. __main__.__spec__

В зависимости от того, как инициализируется __main__, __main__.__spec__ устанавливается соответствующим образом или в None.

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

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

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

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

Обратите также внимание, что даже когда __main__ соответствует импортируемому модулю и __main__.__spec__ устанавливается соответственно, они по-прежнему считаются разными модулями. Это связано с тем, что блоки, защищённые проверками if __name__ == "__main__":, выполняют код только при использовании модуля для заполнения пространства имен __main__, а не во время обычного импорта.

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.

3

В устаревшем коде можно найти экземпляры imp.NullImporter в sys.path_importer_cache. Рекомендуется изменить код на использование None вместо этого. См. Перенос кода Python для получения дополнительной информации.

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

Spec-Zone.ru

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