怎么在Python代码中获取已安装包的列表
详细讲解在Python代码中如何获取当前环境中所有已安装包的名称与版本号。涵盖现代标准库importlib.metadata的完整用法、底层.dist-info元数据发现原理、subprocess与pkg_resources方案对比及避坑指南。
importlib.metadata 标准库。只需调用 importlib.metadata.distributions(),即可直接遍历当前运行环境中所有分发包的名称与版本号,整个过程在 Python 进程内完成,无需启动子进程,也不依赖任何外部第三方工具库。
一、首选方案:使用标准库 importlib.metadata
在过去,很多开发者习惯通过调用外部命令行或者导入 pkg_resources 来查看安装环境。自 Python 3.8 起,官方引入了标准库 importlib.metadata(基于 PEP 566 与 PEP 592),正式取代了以往非标准或开销巨大的方案。
1. 获取全量包名与版本清单
以下是直接在 Python 代码中获取所有已安装包名称及对应版本的标准写法:
import importlib.metadata
# 获取当前环境中所有分发包的迭代器
dists = importlib.metadata.distributions()
# 按包名不区分大小写排序并打印
installed_packages = []
for dist in dists:
name = dist.metadata['Name']
version = dist.version
installed_packages.append((name, version))
installed_packages.sort(key=lambda x: x[0].lower())
for name, ver in installed_packages:
print(f"{name} == {ver}")
charset-normalizer == 3.3.2
idna == 3.7
pip == 24.1.2
requests == 2.32.3
urllib3 == 2.2.2
在这段代码中,distributions() 返回一个由 Distribution 对象构成的可迭代集合。每个对象包含了该包完整的元数据,其中 dist.metadata['Name'] 是包在 Python 官方包索引(PyPI)注册的正式分发名称,而 dist.version 则是已安装的具体版本号。
在 Python 3.10 及以上版本中,Distribution 对象新增了直接属性 dist.name,无需通过字典下标 dist.metadata['Name'] 即可获取名称。如果代码需要同时兼顾 Python 3.8、3.9 与 3.10+,推荐使用 getattr(dist, 'name', None) or dist.metadata['Name'] 进行兼容读取。
二、底层机制:Python 如何识别“已安装包”
很多初学者容易将“已安装的包”与“已载入内存的模块”混淆,误以为从 sys.modules 中就能检索出所有包。理解 Python 的包发现机制,有助于我们在代码中写出更健壮的环境自检逻辑。
当执行 pip install 安装第三方库时,pip 会将两个核心要素写入 Python 解释器的 site-packages 目录:
- 代码目录/模块文件:包含实际运行 Python 代码的文件夹(例如
requests/)。 - 分发元数据目录:以
.dist-info结尾的文件夹(例如requests-2.32.3.dist-info/)。该目录下包含标准文本文件METADATA,详细记录了包名、版本、依赖列表、平台架构等信息。
importlib.metadata.distributions() 的执行本质,就是按照 sys.path 包含的路径列表,在所有搜索路径下扫描存在的 *.dist-info 和 *.egg-info 目录,并解析其中的 METADATA 描述文件。它完全不需要将包的代码导入到内存中,因此执行速度极快(通常在 10 到 30 毫秒以内),且绝不会触发第三方包顶层模块中可能包含的副作用代码。
在 Python 生态中,分发包名称(Distribution Name)与实际在代码中 import 的模块名并不总是一致:
- 分发包名为 PyYAML,但在代码中导入的是
import yaml。 - 分发包名为 Pillow,但在代码中导入的是
import PIL。 - 分发包名为 opencv-python,但在代码中导入的是
import cv2。
distributions() 获取的是分发包的名字与版本。若需要在代码中根据用户 import 的顶层模块名反查其所属分发包,Python 3.10+ 提供了 importlib.metadata.packages_distributions():
# Python 3.10+ 内置反查映射
mapping = importlib.metadata.packages_distributions()
print(mapping.get('PIL')) # 输出: ['Pillow']
print(mapping.get('yaml')) # 输出: ['PyYAML']
三、方案对比:importlib.metadata、subprocess 与 pkg_resources
在实际项目和历史代码库中,我们可能会见到多种获取包列表的手法。了解它们的区别有助于根据实际业务场景做出合理的工程选型。
1. 备选方案:通过 subprocess 调用 pip list(JSON 格式)
如果你的需求是获取绝对与当前环境命令行下 pip list 完全一致的输出,或者需要检查外部某个指定路径的 Python 解释器环境,可以使用 subprocess:
import json
import subprocess
import sys
def get_installed_via_pip():
# 使用 sys.executable 确保调用的是当前正在运行的解释器对应的 pip
cmd = [sys.executable, "-m", "pip", "list", "--format=json"]
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
return json.loads(result.stdout)
packages = get_installed_via_pip()
for item in packages:
print(f"{item['name']} : {item['version']}")
适用场景与代价:该方案的好处是格式由 pip 统一保障,并且天然支持 --editable 或外部环境隔离;缺点是每次调用都需要启动一个全新的操作系统子进程,耗时通常在 200~500 毫秒以上。此外,如果生产环境出于精简目的卸载了 pip(例如部分最小化 Docker 镜像),该调用会直接报错。
2. 历史方案:pkg_resources(已弃用,不建议新项目使用)
在老旧的 Python 教程中经常看到如下代码:
# 不推荐在新项目中继续使用
import pkg_resources
installed = {pkg.key: pkg.version for pkg in pkg_resources.working_set}
为什么必须淘汰:pkg_resources 属于 setuptools 的一部分。在较新的 setuptools 版本中,官方已明确将其标记为 Deprecated(弃用)。导入 pkg_resources 时会深度扫描所有环境并构建复杂的运行时依赖图,不仅显著拖慢 Python 程序的启动时间,还会触发弃用告警。所有现有项目均应逐步迁移至 importlib.metadata。
四、进阶实用场景与代码实现
1. 快速检查单个包是否安装及获取版本
如果仅需检测某个具体包是否存在,无需遍历全量列表,直接调用 version() 函数即可。注意未安装时会抛出 PackageNotFoundError:
import importlib.metadata
def check_package_version(package_name: str):
try:
ver = importlib.metadata.version(package_name)
return ver
except importlib.metadata.PackageNotFoundError:
return None
version = check_package_version("requests")
if version:
print(f"requests 已安装,版本为 {version}")
else:
print("requests 尚未安装")
2. 获取指定包声明的依赖项
通过 requires() 可以读取指定包在打包时声明的下游依赖要求(包括版本限制与条件标记):
import importlib.metadata
reqs = importlib.metadata.requires("requests")
if reqs:
for r in reqs:
print(r)
idna<4,>=2.5
urllib3<3,>=1.21.1
certifi>=2017.4.17
3. 生成环境包依赖字典(用于系统诊断或上报)
如果需要在服务启动时收集诊断指标,可以将已安装包整理为规整的键值字典:
import importlib.metadata
def get_installed_packages_dict():
pkg_dict = {}
for dist in importlib.metadata.distributions():
name = getattr(dist, 'name', None) or dist.metadata['Name']
# 按照 PEP 503 规范将下划线统一为中划线并转小写,避免命名差异
normalized_name = name.lower().replace("_", "-")
pkg_dict[normalized_name] = dist.version
return pkg_dict
env_summary = get_installed_packages_dict()
print(f"当前共安装 {len(env_summary)} 个分发包")
五、工程实践常见问题与注意事项
-
虚拟环境感知:Python 代码检测的包列表始终与当前正在运行的 Python 解释器(
sys.executable)保持一致。如果你在激活了 Virtualenv 或 Conda 的环境中运行脚本,获取到的就是该虚拟环境的独立包列表;若在多解释器服务器上部署,需确保启动脚本使用的是目标虚拟环境的 Python 路径。 -
以可编辑模式(Editable /
pip install -e .)安装的本地开发包:现代 pip 支持 PEP 660 可编辑安装标准,会在site-packages下生成包含__editable__.*.pth或.dist-info元数据,importlib.metadata.distributions()同样能够准确检索并返回其版本号与名称。 -
Python 3.7 环境下的后向兼容:如果必须在已停止支持的 Python 3.7 上运行,可以通过
pip install importlib-metadata安装官方 backport 库,然后在代码中使用import importlib_metadata as metadata,其 API 与现代 Python 内置标准库完全一致。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









