使用 importlib.metadata — Python 文档

来自菜鸟教程
Python/docs/3.10/library/importlib.metadata
跳转至:导航、​搜索

使用 importlib.metadata

3.8 版中的新功能。


在 3.10 版更改:importlib.metadata 不再是临时的。


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

importlib.metadata 是一个用于访问已安装包元数据的库。 该库部分构建在 Python 的导入系统上,旨在替换 pkg_resources入口点 API元数据 API 中的类似功能。 连同 Python 3.7 和更新版本中的 importlib.resources(对于旧版本的 Python,反向移植为 importlib_resources),这可以消除使用旧版本和效率较低的 pkg_resources ] 包裹。

我们所说的“已安装包”一般是指通过pip等工具安装到Pythonsite-packages目录下的第三方包。 具体来说,它意味着一个包具有可发现的 dist-infoegg-info 目录,以及由 PEP 566 或其旧规范定义的元数据。 默认情况下,包元数据可以存在于文件系统或 sys.path 上的 zip 存档中。 通过扩展机制,元数据几乎可以存在于任何地方。

概述

假设您想获取使用 pip 安装的软件包的版本字符串。 我们首先创建一个虚拟环境并在其中安装一些东西:

$ python3 -m venv example
$ source example/bin/activate
(example) $ pip install wheel

您可以通过运行以下命令获取 wheel 的版本字符串:

(example) $ python
>>> from importlib.metadata import version  
>>> version('wheel')  
'0.32.3'

您还可以获取按组键控的入口点集,例如 console_scriptsdistutils.commands 等。 每个组包含一系列 EntryPoint 对象。

您可以获得发行版 元数据:

>>> list(metadata('wheel'))  
['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 属性的组件。

查询所有入口点:

>>> eps = entry_points()

entry_points() 函数返回一个 EntryPoints 对象,为方便起见,所有 EntryPoint 对象具有 namesgroups 属性的序列:

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

EntryPoints 有一个 select 方法来选择匹配特定属性的入口点。 在 console_scripts 组中选择入口点:

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

等效地,由于 entry_points 将关键字参数传递给 select:

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

挑选一个名为“wheel”的特定脚本(在wheel项目中找到):

>>> 'wheel' in scripts.names  
True
>>> wheel = scripts['wheel']

等效地,在选择期间查询该入口点:

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

检查已解决的入口点:

>>> wheel  
EntryPoint(name='wheel', value='wheel.cli:main', group='console_scripts')
>>> wheel.module  
'wheel.cli'
>>> wheel.attr  
'main'
>>> wheel.extras  
[]
>>> main = wheel.load()  
>>> main  
<function main at 0x103528488>

groupname 是由包作者定义的任意值,通常客户端希望解析特定组的所有入口点。 阅读 setuptools 文档 以获取有关入口点、它们的定义和用法的更多信息。

兼容性说明

“可选”入口点是在 importlib_metadata 3.6 和 Python 3.10 中引入的。 在这些更改之前,entry_points 不接受任何参数,并且总是返回一个入口点字典,以组为键。 为了兼容性,如果没有参数传递给 entry_points,则返回 SelectableGroups 对象,实现该 dict 接口。 将来,不带参数调用 entry_points 将返回一个 EntryPoints 对象。 用户应依靠选择界面按组检索入口点。


分发元数据

每个发行版都包含一些元数据,您可以使用 metadata() 函数提取这些元数据:

>>> wheel_metadata = metadata('wheel')

返回的数据结构的键,a PackageMetadata,命名元数据关键字,并且从分布元数据未解析返回值:

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

PackageMetadata 还提供了一个 json 属性,该属性按照 PEP 566 以与 JSON 兼容的形式返回所有元数据:

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

在 3.10 版中更改:Description 现在通过有效负载呈现时包含在元数据中。 行继续符已被删除。


3.10 新功能: 增加了 json 属性。


分发版本

version() 函数是获取发行版版本号的最快方法,作为字符串:

>>> version('wheel')  
'0.32.3'

分发文件

您还可以获得发行版中包含的完整文件集。 files() 函数采用分发包名称并返回此分发安装的所有文件。 返回的每个文件对象是一个 PackagePath、一个 pathlib.PurePath 派生对象,具有额外的 distsizehash 属性,如元数据表示。 例如:

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

获得文件后,您还可以阅读其内容:

>>> print(util.read_text())  
import base64
import sys
...
def as_bytes(s):
    if isinstance(s, text_type):
        return s.encode('utf-8')
    return s

您还可以使用 locate 方法获取文件的绝对路径:

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

在元数据文件列表文件(RECORD 或 SOURCES.txt)丢失的情况下,files() 将返回 None。 调用者可能希望在 always_iterable 中包装对 files() 的调用,或者如果不知道目标分布是否存在元数据,则防止出现这种情况。


配送要求

要获得发行版的完整要求,请使用 requires() 函数:

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

包分发

一种解决顶级 Python 包或模块的分发或分发(在命名空间包的情况下)的便捷方法:

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

3.10 版中的新功能。


分布

虽然上述 API 是最常见和最方便的用法,但您可以从 Distribution 类中获取所有这些信息。 Distribution 是一个抽象对象,表示 Python 包的元数据。 你可以得到 Distribution 实例:

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

因此,获取版本号的另一种方法是通过 Distribution 实例:

>>> dist.version  
'0.32.3'

Distribution 实例上有各种可用的附加元数据:

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

此处未描述完整的可用元数据集。 有关其他详细信息,请参阅 PEP 566


扩展搜索算法

因为包元数据无法通过 sys.path 搜索或包加载器直接获得,所以包的元数据是通过导入系统 finders 找到的。 要查找分发包的元数据,importlib.metadata 查询 sys.meta_path 上的 元路径查找器 列表。

Python 的默认 PathFinder 包括一个调用 importlib.metadata.MetadataPathFinder 的钩子,用于查找从典型的基于文件系统的路径加载的发行版。

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

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

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

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

脚注