importlib.metadata -- 访问软件包元数�¶
在 3.8 ç‰ˆæœ¬åŠ å…¥.
在 3.10 版本�生�更: importlib.metadata ��是暂定的。
æº�代ç �: Lib/importlib/metadata/__init__.py
importlib.metadata 是一个æ��供对已安装的 分å�‘包 的元数æ�®çš„访问的库,如其入å�£ç‚¹æˆ–其最高层级å��ç§° (导入包, 模å�—ç‰ï¼Œå¦‚æžœå˜åœ¨çš„è¯�)。 这个库部分构建于 Python çš„å¯¼å…¥ç³»ç»Ÿä¹‹ä¸Šï¼Œå…¶ç›®æ ‡æ˜¯å�–代 pkg_resources çš„ä¸çš„ entry point API å’Œ metadata API。 é…�å�ˆ importlib.resources ,这个包å�¯ä»¥æ¶ˆé™¤ä½¿ç”¨è¾ƒè€�旧且低效的 pkg_resources 包的必è¦�性。
importlib.metadata 对通过 pip 之类的工具安装到 Python çš„ site-packages 目录的第三方 分å�‘包 进行æ“�作。 具体æ�¥è¯´ï¼Œå®ƒé€‚用于带有å�¯å�‘现 dist-info 或 egg-info 目录,以å�Šç”± æ ¸å¿ƒå…ƒæ•°æ�®è§„范说明 所定义的元数æ�®çš„分å�‘包。
��
这些 å¹¶ä¸� å¿…é¡»ç‰å�ŒäºŽæˆ– 1:1 对应于å�¯åœ¨ Python 代ç �ä¸å¯¼å…¥çš„æœ€é«˜å±‚级 导入包 å��称。 一个 分å�‘包 å�¯ä»¥åŒ…å�«å¤šä¸ª 导入包 (å’Œå�•独模å�—),而一个最高层级 导入包 如果是命å��空间包则å�¯ä»¥æ˜ 射到多个 分å�‘包。 ä½ å�¯ä»¥ä½¿ç”¨ package_distributions() æ�¥èŽ·å�–å®ƒä»¬ä¹‹é—´çš„æ˜ å°„å…³ç³»ã€‚
在默认情况下,分å�‘包元数æ�®å�¯ä»¥å˜åœ¨äºŽ sys.path 下的文件系统或 zip 归档文件ä¸ã€‚ 通过一个扩展机制,元数æ�®å�¯ä»¥å˜åœ¨äºŽå‡ 乎任何地方。
��
- https://importlib-metadata.readthedocs.io/
importlib_metadata的文档,它�供了对importlib.metadata的�下移�。 这包�该模�的类和函数的 API 引用,以�针对pkg_resources现有用户的 �移指�。
概述¶
让我们å�‡è®¾ä½ 想è¦�获å�–ä½ ä½¿ç”¨ pip 安装的æŸ�个 分å�‘包 的版本å—符串。 我们首先创建一个虚拟环境并在其ä¸å®‰è£…一些软件包:
$ 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'
ä½ è¿˜èƒ½å¤ŸèŽ·å¾—å�¯é€šè¿‡ EntryPoint 的特å¾�属性 (通常为 'group' 或 'name') æ�¥é€‰æ‹©çš„å…¥å�£ç‚¹å¤šé¡¹é›†ï¼Œæ¯”如 console_scripts, distutils.commands ç‰ç‰ã€‚ æ¯�个 group 包å�«ä¸€ä¸ªç”± 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 对象,�由带有 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 ä¼ é€’å…³é”®å—å�‚æ•° "group" 以实现相å�Œçš„æ•ˆæžœ:
>>> 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>
group 和 name 是由包作者定义的任�值并且通常�说客户端会想�解�特定 group 的所有入�点。 请�阅 the setuptools docs 了解有关入�点,其定义和用法的更多信�。
兼容性说明
"selectable" å…¥å�£ç‚¹æ˜¯åœ¨ importlib_metadata 3.6 å’Œ Python 3.10 ä¸å¼•入的。 在这项改å�˜ä¹‹å‰�,entry_points ä¸�接å�—任何形å�‚并且总是返回一个由入å�£ç‚¹ç»„æˆ�çš„å—典,å—典的键为分组å��。 在 importlib_metadata 5.0 å’Œ Python 3.12 ä¸ï¼Œentry_points 总是返回一个 EntryPoints 对象。 请å�‚阅 backports.entry_points_selectable 了解相关兼容性选项。
分�的元数�¶
æ¯�个 分å�‘包 都包括一些元数æ�®ï¼Œä½ å�¯ä»¥ä½¿ç”¨ metadata() 函数æ�¥èŽ·å�–:
>>> wheel_metadata = metadata('wheel')
返回的数æ�®æž¶æž„ PackageMetadata 的键代表元数æ�®çš„关键å—,而值从分å�‘的元数æ�®ä¸ä¸�被解æž�地返回:
>>> 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.*'
备注
metadata() 所返回的对象的实际类型是一个实现细节并且应当�能通过 PackageMetadata �议 所�述的接��访问。
在 3.10 版本å�‘生å�˜æ›´: 当有效载è�·ä¸åŒ…å�«æ—¶ï¼ŒDescription 以去除ç»è¡Œç¬¦çš„å½¢å¼�被包å�«äºŽå…ƒæ•°æ�®ä¸ã€‚
在 3.10 ç‰ˆæœ¬åŠ å…¥: æ·»åŠ äº† json 属性。
分�的版本¶
version() 函数å�¯ä»¥æœ€å¿«æ�·åœ°ä»¥å—符串形å¼�获å�–一个 分å�‘包 的版本å�·:
>>> version('wheel')
'0.32.3'
分�的文件¶
ä½ è¿˜å�¯ä»¥èŽ·å�–包å�«åœ¨åˆ†å�‘包内的全部文件的集å�ˆã€‚ files() 函数接å�—一个 分å�‘包 å��称并返回æ¤åˆ†å�‘包所安装的全部文件。 æ¯�个返回的文件对象都是一个 PackagePath,å�³å¸¦æœ‰ç”±å…ƒæ•°æ�®æŒ‡æ˜Žçš„é¢�外 dist, size å’Œ hash 特å¾�属性的派生自 pathlib.PurePath 的对象。 例如:
>>> 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 ä¸ï¼Œæˆ–者用其他方法æ�¥åº”å¯¹ç›®æ ‡åˆ†å�‘元数æ�®å˜åœ¨æ€§æœªçŸ¥çš„æƒ…况。
分�的�赖¶
�获�一个 分�包 的完整需求集�,请使用 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'
å�¯ç”¨å…ƒæ•°æ�®çš„完整集å�ˆå¹¶æœªåœ¨æ¤æ��述。 请å�‚阅 æ ¸å¿ƒå…ƒæ•°æ�®è§„æ ¼è¯´æ˜Ž 了解更多细节。
分�包的�现¶
在默认情况下,这个包针对文件系统和 zip 文件 分å�‘包 的元数æ�®å�‘现æ��供了内置支æŒ�。 这个元数æ�®æŸ¥æ‰¾å™¨çš„æ�œç´¢ç›®æ ‡é»˜è®¤ä¸º sys.path,但它对æ�¥è‡ªå…¶ä»–导入机制行为方å¼�的解读会略有å�˜åŒ–。 特别地:
importlib.metadata�会识别sys.path上的bytes对象。importlib.metadata将顺带识别sys.path上的pathlib.Path对象,�使这些值会被导入�作所忽略。
扩展�索算法¶
å› ä¸º 分å�‘包 元数æ�®ä¸�能通过 sys.path æ�œç´¢ï¼Œæˆ–æ˜¯é€šè¿‡åŒ…åŠ è½½å™¨ç›´æŽ¥èŽ·å¾—ï¼Œä¸€ä¸ªåˆ†å�‘包的元数æ�®æ˜¯é€šè¿‡å¯¼å…¥ç³»ç»Ÿçš„ æŸ¥æ‰¾å™¨ 找到的。 è¦�找到分å�‘包的元数æ�®ï¼Œimportlib.metadata 将在 sys.meta_path 上查询 元路径查找器 的列表。
在默认情况下 importlib.metadata ä¼šå®‰è£…åœ¨æ–‡ä»¶ç³»ç»Ÿä¸æ‰¾åˆ°çš„分å�‘包的查找器。 è¿™ä¸ªæŸ¥æ‰¾å™¨æ— æ³•çœŸæ£æ‰¾å‡ºä»»ä½• 分å�‘包,但它能找到它们的元数æ�®ã€‚
抽象基类 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 实例。