importlib.metadata
-- 访问软件包元数据¶
Added in version 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
对 pip 等工具安装到 Python 的 site-packages
目录的第三方 分发包 进行操作。 具体来说,适用的分发包应带有可发现的 dist-info
或 egg-info
目录,以及 核心元数据规范说明 定义的元数据。
重要
它们 不一定 等同或 1:1 对应于可在 Python 代码中导入的顶层 导入包 名称。一个 分发包 可以包含多个 导入包 (和单个模块),如果是命名空间包,一个顶层 导入包 可以映射到多个 分发包。您可以使用 packages_distributions() 来获取它们之间的映射。
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/
importlib_metadata
的文档,它向下移植了importlib.metadata
。它包含该模块的类和函数的 API 参考,以及针对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:
$ python -m venv example
$ source example/bin/activate
(example) $ python -m pip install wheel
你可以通过运行以下代码得到 wheel
的版本字符串:
(example) $ python
>>> from importlib.metadata import version
>>> version('wheel')
'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.
你可以获得 分发的元数据:
>>> 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']
你也可以获得 分发包的版本号,列出它的 构成文件,并且得到分发包的 分发包的依赖 列表。
- exception importlib.metadata.PackageNotFoundError¶
当查询未在当前 Python 环境中安装的分发包时由此模块的某些函数所引发的
ModuleNotFoundError
子类。
函数式 API¶
这个包的公开 API 提供了以下功能。
入口点¶
- importlib.metadata.entry_points(**select_params)¶
返回一个描述当前环境的入口点的
EntryPoints
实例。 所给出的任何关键字形参都将被传给select()
方法以与单独的入口点定义的属性进行比较。注意:目前无法基于
EntryPoint.dist
属性来查询入口点(因为不同的Distribution
实例目前不可能相等,即使它们具有相同的属性)
- class importlib.metadata.EntryPoints¶
已安装入口点多项集的详情。
还提供
.groups
属性用于报告所有已标识的入口点分组,以及.names
属性用于报告所有已标识的入口点名称。
- class importlib.metadata.EntryPoint¶
一个已安装入口点的详情。
每个
EntryPoint
实例都有.name
,.group
和.value
属性以及.load()
方法用于求值。 此外还有.module
,.attr
和.extras
属性用于获取.value
属性的组成部分,以及.dist
用于获取有关提供该入口点的分发包的信息。
查询所有的入口点:
>>> eps = entry_points()
entry_points()
函数返回一个 EntryPoints
对象,即由带 names
和 groups
属性的所有 EntryPoint
对象组成的多项集以方便使用:
>>> 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()
会传递关键字参数来选择:
>>> 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>
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.
在 3.12 版本发生变更: "selectable" 入口点是在 importlib_metadata
3.6 和 Python 3.10 中引入的。 在这项改变之前,entry_points
不接受任何形参并且总是返回一个由入口点组成的字典,字典的键为分组名。 在 importlib_metadata
5.0 和 Python 3.12 中,entry_points
总是返回一个 EntryPoints
对象。 请参阅 backports.entry_points_selectable 了解相关兼容性选项。
在 3.13 版本发生变更: EntryPoint
对象不再提供类似于元组的接口(__getitem__()
)。
分发的元数据¶
- importlib.metadata.metadata(distribution_name)¶
将对应于指定分发包的分发元数据作为
PackageMetadata
实例返回。如果指定的发布包未在当前 Python 环境中安装则会引发
PackageNotFoundError
。
- class importlib.metadata.PackageMetadata¶
PackageMetadata 协议 的一个具体实现。
除了提供已定义的协议方法和属性,对实例的下标操作就相当于调用
get()
方法。
每个 分发包 都包括一些元数据,你可以使用 metadata()
函数来获取:
>>> wheel_metadata = metadata('wheel')
所返回数据结构的键指明了元数据关键字,而值将从分发的元数据中不加解析地返回:
>>> wheel_metadata['Requires-Python']
'>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
PackageMetadata
也还提供了一个按照 PEP 566 将所有元数据以 JSON 兼容形式返回的 json
:
>>> wheel_metadata.json['requires_python']
'>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
此处并未描述可用元数据的完整集合。 更多详情参见 PyPA 核心元数据规格说明。
在 3.10 版本发生变更: 当有效载荷中包含时,Description
以去除续行符的形式被包含于元数据中。
添加了 json
属性。
分发包的版本¶
- importlib.metadata.version(distribution_name)¶
返回指定分发包的已安装分发包版本。
如果指定的发布包未在当前 Python 环境中安装则会引发
PackageNotFoundError
。
version()
函数是获取字符串形式的 分发包 版本号的最快速方式:
>>> version('wheel')
'0.32.3'
分发包的文件¶
- importlib.metadata.files(distribution_name)¶
返回包含在指定分发包内的完整文件集合。
如果指定的发布包未在当前 Python 环境中安装则会引发
PackageNotFoundError
。如果找到了分发包但未找到报告与分发包相关联的文件的安装数据库记录则返回
None
。
- class importlib.metadata.PackagePath¶
一个
pathlib.PurePath
的派生对象,增加了对应于指定文件的分发包的安装元数据的dist
,size
和hash
特征属性。
files()
函数接受一个 分发包 名称并返回此分发包所安装的全部文件。 每个文件均报告为一个 PackagePath
实例。 例如:
>>> 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
。 调用者可能会想要把对 files()
的调用包装在 always_iterable 中或是用其他方式在尚未知晓目标分发元数据存在性时应对此情况。
分发包的依赖¶
- importlib.metadata.requires(distribution_name)¶
返回指定分发包已声明的依赖描述。
如果指定的发布包未在当前 Python 环境中安装则会引发
PackageNotFoundError
。
要获取一个 分发包 的完整需求集,请使用 requires()
函数:
>>> requires('wheel')
["pytest (>=3.0.0) ; extra == 'test'", "pytest-cov ; extra == 'test'"]
将导入映射到分发包¶
- importlib.metadata.packages_distributions()¶
返回一个从最高层级模块和通过
sys.meta_path
找到的导入包名称到提供相应文件的分发包名称(如果存在)的映射。为了允许使用命名空间包(它可能包含由多个分发包所提供的成员),每个最高层级导入名称都映射到一个分发名称的列表而不是直接映射单个名称。
解析每个提供可导入的最高层级 Python 模块或 导入包 对应的 分发包 名称(对于命名空间包可能有多个名称)的快捷方法:
>>> packages_distributions()
{'importlib_metadata': ['importlib-metadata'], 'yaml': ['PyYAML'], 'jaraco': ['jaraco.classes', 'jaraco.functools'], ...}
某些可编辑的安装 没有提供最高层级名称,因而此函数不适用于这样的安装。
Added in version 3.10.
分发包对象¶
- importlib.metadata.distribution(distribution_name)¶
返回一个描述指定分发包的
Distribution
实例。instance describing the named distribution package.如果指定的发布包未在当前 Python 环境中安装则会引发
PackageNotFoundError
。
- class importlib.metadata.Distribution¶
一个已安装分发包的详情。
注意:目前不同的
Distribution
实例在比较时肯定不相等,即使它们是关联到相同的已安装发布版因而具有相同的属性。
虽然上面描述的模块级 API 是最常见且便捷的用法,但你也可以从 Distribution
类获取所有信息。 Distribution
是一个代表 Python 分发包 元数据的抽象对象。 你可以通过调用 distribution()
函数来获取对应于某个已安装分发包的具体 Distribution
子类实例:
>>> from importlib.metadata import distribution
>>> dist = distribution('wheel')
>>> type(dist)
<class 'importlib.metadata.PathDistribution'>
因此,一个获取版本号的替代方式是通过 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'
对于可编辑包,origin
属性可能表示 PEP 610 元数据:
>>> dist.origin.url
'file:///path/to/wheel-0.32.3.editable-py3-none-any.whl'
此处并未描述可用元数据的完整集合。 更多详情参见 PyPA 核心元数据规格说明。
Added in version 3.13: 增加了 .origin
特征属性。
分发包的发现¶
在默认情况下,这个包针对文件系统和 zip 文件 分发包 的元数据发现提供了内置支持。 这个元数据查找器的搜索目标默认为 sys.path
,但它对来自其他导入机制行为方式的解读会略有变化。 特别地:
importlib.metadata
不会识别sys.path
上的bytes
对象。importlib.metadata
将顺带识别sys.path
上的pathlib.Path
对象,即使这些值会被导入操作所忽略。
Implementing Custom Providers¶
importlib.metadata
address two API surfaces, one for consumers
and another for providers. Most users are consumers, consuming
metadata provided by the packages. There are other use-cases, however,
where users wish to expose metadata through some other mechanism,
such as alongside a custom importer. Such a use case calls for a
custom provider.
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
.
The implementation has hooks integrated into the PathFinder
,
serving metadata for distribution packages found on the file system.
抽象基类 importlib.abc.MetaPathFinder
定义了 Python 导入系统期望的查找器接口。 importlib.metadata
通过寻找 sys.meta_path
上查找器可选的 find_distributions
可调用的属性扩展这个协议,并将这个扩展接口作为 DistributionFinder
抽象基类提供,它定义了这个抽象方法:
@abc.abstractmethod
def find_distributions(context=DistributionFinder.Context()) -> Iterable[Distribution]:
"""Return an iterable of all Distribution instances capable of
loading the metadata for packages for the indicated ``context``.
"""
The DistributionFinder.Context
object provides .path
and .name
properties indicating the path to search and name to match and may
supply other relevant context sought by the consumer.
In practice, to support finding distribution package
metadata in locations other than the file system, subclass
Distribution
and implement the abstract methods. Then from
a custom finder, return instances of this derived Distribution
in the
find_distributions()
method.
示例¶
Imagine a custom finder that loads Python modules from a database:
class DatabaseImporter(importlib.abc.MetaPathFinder):
def __init__(self, db):
self.db = db
def find_spec(self, fullname, target=None) -> ModuleSpec:
return self.db.spec_from_name(fullname)
sys.meta_path.append(DatabaseImporter(connect_db(...)))
该导入器现在大概可以从数据库中导入模块,但它不提供元数据或入口点。这个自定义导入器如果要提供元数据,它还需要实现 DistributionFinder
:
from importlib.metadata import DistributionFinder
class DatabaseImporter(DistributionFinder):
...
def find_distributions(self, context=DistributionFinder.Context()):
query = dict(name=context.name) if context.name else {}
for dist_record in self.db.query_distributions(query):
yield DatabaseDistribution(dist_record)
这样一来,query_distributions
就会返回数据库中与查询匹配的每个分发包的记录。例如,如果数据库中有 requests-1.0
,find_distributions
就会为 Context(name='requests')
或``Context(name=None)`` 产生 DatabaseDistribution
。
为简单起见,本例忽略了 context.path
。path
属性默认为 sys.path
,是搜索中考虑的导入路径集。一个 DatabaseImporter
可以在不考虑搜索路径的情况下运作。假设导入器不进行分区,那么 "path" 就无关紧要了。为了说明 path
的作用,示例需要展示一个更复杂的 DatabaseImporter
,它的行为随 sys.path
/PYTHONPATH
而变化。在这种情况下,find_distributions
应该尊重 context.path
,并且只产生与该路径相关的 Distribution
。
那么,DatabaseDistribution
看起来就像是这样:
class DatabaseDistribution(importlib.metadata.Distribution):
def __init__(self, record):
self.record = record
def read_text(self, filename):
"""
为当前分发版读取文件型的 "METADATA"。
"""
if filename == "METADATA":
return f"""Name: {self.record.name}
Version: {self.record.version}
"""
if filename == "entry_points.txt":
return "\n".join(
f"""[{ep.group}]\n{ep.name}={ep.value}"""
for ep in self.record.entry_points)
def locate_file(self, path):
raise RuntimeError("This distribution has no file system")
这个基本实现应当为由 DatabaseImporter
进行服务的包提供元数据和入口点,假定 record
提供了适当的 .name
, .version
和 .entry_points
属性。
DatabaseDistribution
还可能提供其他元数据文件,比如 RECORD
(对 Distribution.files
来说需要) 或重写 Distribution.files
的实现。 请参看源代码深入了解。