Using importlib.metadata

Note

此Function是临时的,可能会偏离标准库的常规版本语义。

importlib.metadata是用于访问已安装的软件包元数据的库。该库部分构建在 Python 的导入系统上,旨在替换pkg_resources入口点 APImetadata API中的类似Function。与Python 3.7 及更高版本中的importlib.resources(对于较旧版本的 Python 反向移植为importlib_resources)一起,这可以消除使用较旧且效率较低的pkg_resources软件包的需要。

“安装的软件包”通常是指pass诸如pip之类的工具安装到 Python 的site-packages目录中的第三方软件包。具体来说,它表示具有可发现的dist-infoegg-info目录以及由PEP 566或其较旧规范定义的元数据的软件包。默认情况下,程序包元数据可以存在于文件系统上,也可以存在于sys.path的 zip 归档文件中。pass扩展机制,元数据几乎可以存在于任何地方。

Overview

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

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

您可以pass运行以下命令获取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']

您还可以获取发行版本号,列出其constituent files,并获取发行版的Distribution requirements的列表。

Functional API

该软件包pass其公共 API 提供以下Function。

Entry points

entry_points()函数返回按组键的所有入口点的字典。入口点由EntryPoint个实例表示;每个EntryPoint具有.name.group.value属性,以及.load()方法来解析该值。

>>> eps = entry_points()  
>>> list(eps)  
['console_scripts', 'distutils.commands', 'distutils.setup_keywords', 'egg_info.writers', 'setuptools.installation']
>>> scripts = eps['console_scripts']  
>>> wheel = [ep for ep in scripts if ep.name == 'wheel'][0]  
>>> wheel  
EntryPoint(name='wheel', value='wheel.cli:main', group='console_scripts')
>>> main = wheel.load()  
>>> main  
<function main at 0x103528488>

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

Distribution metadata

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

>>> wheel_metadata = metadata('wheel')

返回的数据结构[1]的键命名为 metadata 关键字,并且它们的值是从分发元数据中解析而来的:

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

Distribution versions

version()函数是获取发行版版本号(以字符串形式)的最快方法:

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

Distribution files

您还可以获取分发中包含的完整文件集。 files()函数采用分发包名称,并返回此分发安装的所有文件。返回的每个文件对象都是PackagePathpathlib.Path派生对象,具有附加的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

在缺少元数据文件列表文件(RECORD 或 SOURCES.txt)的情况下,files()将返回None。调用者可能希望将对files()的调用包装在always_iterable中,否则,如果未知目标分发中存在元数据,则可以避免这种情况。

Distribution requirements

要获得分发的全部要求,请使用requires()函数:

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

Distributions

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

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

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

>>> dist.version  
'0.32.3'

Distribution实例上还有各种其他可用的元数据:

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

这里没有描述完整的可用元数据集。有关更多详细信息,请参见PEP 566

扩展搜索算法

由于无法passsys.path搜索或直接从软件包加载程序获得软件包元数据,因此可以pass导入系统finders找到软件包的元数据。要查找分发程序包的元数据,importlib.metadatasys.meta_path上查询元路径发现者的列表。

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

抽象类importlib.abc.MetaPathFinder定义了 Python 导入系统期望的查找器接口。 importlib.metadatapass在sys.meta_path上的查找器上查找可选的find_distributions来扩展此协议,并将此扩展接口作为DistributionFinder抽象 Base Class 呈现,该抽象 Base Class 定义了此抽象方法:

@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的实例。

Footnotes

  • [1]
    • 从技术上讲,返回的分发元数据对象是email.message.Message实例,但这是实现的详细信息,而不是稳定 API 的一部分。您只应使用类似于字典的方法和语法来访问元数据内容。