使用 importlib.metadata

3.8 新版功能.

在 3.10 版更改: importlib.metadata 不再是暂定的。

源代码: Lib/importlib/metadata/__init__.py

importlib_metadata is a library that provides access to the metadata of an installed Distribution Package, such as its entry points or its top-level names (Import Packages, modules, if any). Built in part on Python’s import system, this library intends to replace similar functionality in the entry point API and metadata API of pkg_resources. Along with importlib.resources, this package can eliminate the need to use the older and less efficient pkg_resources package.

importlib_metadata operates on third-party distribution packages installed into Python’s site-packages directory via tools such as pip. Specifically, it works with distributions with discoverable dist-info or egg-info directories, and metadata defined by the Core metadata specifications.

重要

These are not necessarily equivalent to or correspond 1:1 with the top-level import package names that can be imported inside Python code. One distribution package can contain multiple import packages (and single modules), and one top-level import package may map to multiple distribution packages if it is a namespace package. You can use package_distributions() to get a mapping between them.

By default, distribution metadata can live on the file system or in zip archives on sys.path. Through an extension mechanism, the metadata can live almost anywhere.

参见

https://importlib-metadata.readthedocs.io/

The documentation for importlib_metadata, which supplies a backport of importlib.metadata. This includes an API reference for this module’s classes and functions, as well as a migration guide for existing users of pkg_resources.

概述

Let’s say you wanted to get the version string for a Distribution Package you’ve installed using pip. We start by creating a virtual environment and installing something into it:

  1. $ python3 -m venv example
  2. $ source example/bin/activate
  3. (example) $ python -m pip install wheel

你可以通过运行以下代码得到``wheel``的版本字符串:

  1. (example) $ python
  2. >>> from importlib.metadata import version
  3. >>> version('wheel')
  4. '0.32.3'

You can also get a collection of entry points selectable by properties of the EntryPoint (typically ‘group’ or ‘name’), such as console_scripts, distutils.commands and others. Each group contains a collection of EntryPoint objects.

你可以获得 分发的元数据

  1. >>> list(metadata('wheel'))
  2. ['Metadata-Version', 'Name', 'Version', 'Summary', 'Home-page', 'Author', 'Author-email', 'Maintainer', 'Maintainer-email', 'License', 'Project-URL', 'Project-URL', 'Project-URL', 'Keywords', 'Platform', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Requires-Python', 'Provides-Extra', 'Requires-Dist', 'Requires-Dist']

你也可以获得 分发的版本号,列出它的 构成文件,并且得到分发的 分发的依赖 列表。

功能性 API

这个包通过其公共 API 提供了以下功能。

入口点

entry_points() 函数返回入口点的字典。入口点表现为 EntryPoint 的实例;每个 EntryPoint 对象都有 .name.group.value 属性,用于解析值的 .load() 方法, .module.attr.extras 属性是 .value 属性的对应部分。

查询所有的入口点:

  1. >>> eps = entry_points()

The entry_points() function returns an EntryPoints object, a collection of all EntryPoint objects with names and groups attributes for convenience:

  1. >>> sorted(eps.groups)
  2. ['console_scripts', 'distutils.commands', 'distutils.setup_keywords', 'egg_info.writers', 'setuptools.installation']

EntryPointsselect 方法用于选择匹配特性的入口点。要选择 console_scripts 组中的入口点:

  1. >>> scripts = eps.select(group='console_scripts')

你也可以向 entry_points 传递关键字参数 “group” 以实现相同的效果:

  1. >>> scripts = entry_points(group='console_scripts')

选出命名为 “wheel” 的特定脚本(可以在 wheel 项目中找到):

  1. >>> 'wheel' in scripts.names
  2. True
  3. >>> wheel = scripts['wheel']

等价地,在选择过程中查询对应的入口点:

  1. >>> (wheel,) = entry_points(group='console_scripts', name='wheel')
  2. >>> (wheel,) = entry_points().select(group='console_scripts', name='wheel')

检查解析得到的入口点:

  1. >>> wheel
  2. EntryPoint(name='wheel', value='wheel.cli:main', group='console_scripts')
  3. >>> wheel.module
  4. 'wheel.cli'
  5. >>> wheel.attr
  6. 'main'
  7. >>> wheel.extras
  8. []
  9. >>> main = wheel.load()
  10. >>> main
  11. <function main at 0x103528488>

The group and name are arbitrary values defined by the package author and usually a client will wish to resolve all entry points for a particular group. Read the setuptools docs for more information on entry points, their definition, and usage.

兼容性说明

“可选择” 的入口点在 importlib_metadata 3.6,Python 3.10 中被引入。在此之前, entry_points 没有形参且总是返回一个以分组为键,以入口点为值的字典。为了兼容性,如果不带参数地调用 entry_points, 则会返回一个实现了字典接口的 SelectableGroups 对象。未来,不带参数调用 entry_points 会返回 EntryPoints 对象。用户只应该依靠选择接口来按组获得入口点。

分发的元数据

Every Distribution Package includes some metadata, which you can extract using the metadata() function:

  1. >>> wheel_metadata = metadata('wheel')

返回的数据架构 PackageMetadata 的键代表元数据的关键字,而值从分发的元数据中不被解析地返回:

  1. >>> wheel_metadata['Requires-Python']
  2. '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'

PackageMetadata 也提供了按照 PEP 566 将所有元数据以 JSON 兼容的方式返回的 json 属性:

  1. >>> wheel_metadata.json['requires_python']
  2. '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'

备注

The actual type of the object returned by metadata() is an implementation detail and should be accessed only through the interface described by the PackageMetadata protocol.

在 3.10 版更改: 当有效载荷中包含时,Description 以去除续行符的形式被包含于元数据中。

3.10 新版功能: 添加了 json 属性。

分发的版本

The version() function is the quickest way to get a Distribution Package‘s version number, as a string:

  1. >>> version('wheel')
  2. '0.32.3'

分发的文件

You can also get the full set of files contained within a distribution. The files() function takes a Distribution Package name and returns all of the files installed by this distribution. Each file object returned is a PackagePath, a pathlib.PurePath derived object with additional dist, size, and hash properties as indicated by the metadata. For example:

  1. >>> util = [p for p in files('wheel') if 'util.py' in str(p)][0]
  2. >>> util
  3. PackagePath('wheel/util.py')
  4. >>> util.size
  5. 859
  6. >>> util.dist
  7. <importlib.metadata._hooks.PathDistribution object at 0x101e0cef0>
  8. >>> util.hash
  9. <FileHash mode: sha256 value: bYkw5oMccfazVCoYQwKkkemoVyMAFoR34mmKBx8R1NI>

当你获得了文件对象,你可以读取其内容:

  1. >>> print(util.read_text())
  2. import base64
  3. import sys
  4. ...
  5. def as_bytes(s):
  6. if isinstance(s, text_type):
  7. return s.encode('utf-8')
  8. return s

你也可以使用 locate 方法来获得文件的绝对路径:

  1. >>> util.locate()
  2. PosixPath('/home/gustav/example/lib/site-packages/wheel/util.py')

当列出包含文件的元数据文件(RECORD 或 SOURCES.txt)不存在时, files() 函数将返回 None 。调用者可能会想要将对 files() 的调用封装在 always_iterable 中,或者用其他方法来应对目标分发元数据存在性未知的情况。

分发的依赖

To get the full set of requirements for a Distribution Package, use the requires() function:

  1. >>> requires('wheel')
  2. ["pytest (>=3.0.0) ; extra == 'test'", "pytest-cov ; extra == 'test'"]

Mapping import to distribution packages

A convenience method to resolve the Distribution Package name (or names, in the case of a namespace package) that provide each importable top-level Python module or Import Package:

  1. >>> packages_distributions()
  2. {'importlib_metadata': ['importlib-metadata'], 'yaml': ['PyYAML'], 'jaraco': ['jaraco.classes', 'jaraco.functools'], ...}

3.10 新版功能.

分发

While the above API is the most common and convenient usage, you can get all of that information from the Distribution class. A Distribution is an abstract object that represents the metadata for a Python Distribution Package. You can get the Distribution instance:

  1. >>> from importlib.metadata import distribution
  2. >>> dist = distribution('wheel')

因此,可以通过 Distribution 实例获得版本号:

  1. >>> dist.version
  2. '0.32.3'

Distribution 实例具有所有可用的附加元数据:

  1. >>> dist.metadata['Requires-Python']
  2. '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
  3. >>> dist.metadata['License']
  4. 'MIT'

The full set of available metadata is not described here. See the Core metadata specifications for additional details.

Distribution Discovery

By default, this package provides built-in support for discovery of metadata for file system and zip file Distribution Packages. This metadata finder search defaults to sys.path, but varies slightly in how it interprets those values from how other import machinery does. In particular:

  • importlib.metadata does not honor bytes objects on sys.path.

  • importlib.metadata will incidentally honor pathlib.Path objects on sys.path even though such values will be ignored for imports.

扩展搜索算法

Because Distribution Package metadata is not available through sys.path searches, or package loaders directly, the metadata for a distribution is found through import system finders. To find a distribution package’s metadata, importlib.metadata queries the list of meta path finders on sys.meta_path.

By default importlib_metadata installs a finder for distribution packages found on the file system. This finder doesn’t actually find any distributions, but it can find their metadata.

抽象基类 importlib.abc.MetaPathFinder 定义了 Python 导入系统期望的查找器接口。 importlib.metadata 通过寻找 sys.meta_path 上查找器可选的 find_distributions 可调用的属性扩展这个协议,并将这个扩展接口作为 DistributionFinder 抽象基类提供,它定义了这个抽象方法:

  1. @abc.abstractmethod
  2. def find_distributions(context=DistributionFinder.Context()):
  3. """Return an iterable of all Distribution instances capable of
  4. loading the metadata for packages for the indicated ``context``.
  5. """

DistributionFinder.Context 对象提供了指示搜索路径和匹配名称的属性 .path.name ,也可能提供其他相关的上下文。

这在实践中意味着要支持在文件系统外的其他位置查找分发包的元数据,你需要子类化 Distribution 并实现抽象方法,之后从一个自定义查找器的 find_distributions() 方法返回这个派生的 Distribution 实例。