Python查看已安装包时怎么过滤系统内置模块
深入讲解在Python中查看已安装第三方扩展包时如何彻底过滤系统内置模块与官方标准库。对比pip list机制、importlib.metadata正向元数据提取及sys.stdlib_module_names集合差集过滤技术。
help('modules') 或粗暴扫描环境路径,系统会把 sys、builtins、os、json 等几百个系统自带模块全数罗列出来,造成严重的视觉混乱与解析干扰。解决这个问题的核心原则在于“正向读取第三方分发元数据,而非后置遍历大列表”:在终端命令行中,官方推荐的 pip list 默认就只统计第三方包,天然不包含任何内置或标准库模块;在 Python 代码内部,推荐使用 3.8+ 标准库 importlib.metadata.distributions() 从源头提取扩展包;而若是由于动态加载需求必须对全局模块列表进行反向清洗,则可以利用 Python 3.10+ 原生支持的 sys.stdlib_module_names 与 sys.builtin_module_names 集合执行高速的差集过滤。
一、为什么查看已安装包时会混入内置模块?
要彻底解决模块混杂的问题,首先需要理清 Python 内部对“模块(Module)”和“已安装分发包(Distribution Package)”的定义差异。在许多初学者的认知中,只要能够被 import 引入的代码都是“包”,但从底层物理存储与解释器生命周期来看,它们存在严格的三层结构划分:
- C 核心内置模块(Built-in Modules):如
sys、builtins、_ast、gc等。它们直接编译在 Python 解释器二进制内核中,在磁盘上没有独立的.py脚本文件,内存中也没有__file__属性,直接由解释器内部符号表加载。 - Python 官方标准库(Standard Library):如
os、json、urllib、pathlib等。它们是随 Python 官方发行版一同捆绑安装的系统级组件,存放在 Python 安装目录的Lib/文件夹中(不含site-packages),由官方统一维护。 - 第三方已安装分发包(Site-Packages):如
requests、numpy、pandas、flask等。这些包是由用户通过pip install等包管理工具下载并存放在Lib/site-packages/目录下的。每个合规安装的第三方包都会伴随生成一个规范的元数据描述文件夹(如requests-2.31.0.dist-info)。
当用户在交互式终端中执行 help('modules') 时,Python 会沿着 sys.path 中注册的所有路径无差别扫描所有可导入对象,因此底层解释器模块、官方标准库与第三方扩展包会被一股脑混合打印。所谓“过滤系统内置模块”,本质上就是如何将检索范围精准约束在拥有 dist-info 元数据的第三方 site-packages 区域。
二、终端命令行查看:为什么 pip list 天然就是过滤后的结果
如果你的需求是在终端命令行中排查“当前 Python 环境到底装了哪些第三方扩展”,那么答案非常直接:不要使用交互式终端的 help('modules'),直接使用 pip list。
pip 是 Python 的官方分发包管理工具(Packaging Tool),其设计初衷就是管理安装、升级与卸载。它在执行扫描时,只会深入 site-packages 等分发路径去检索规范的 .dist-info 或 .egg-info 记录。由于系统内置模块(如 sys)和标准库(如 json)不是通过 pip 分发的,也不具备分发元数据描述符,因此 pip list 在逻辑上天生就排除了系统内置与标准库模块。
1. 常用命令操作与格式选项
在终端中,建议使用 python -m pip 代替单纯的 pip,以确保检索的包确切属于当前调用的 Python 解释器环境:
# 标准查看已安装第三方包及其版本号
python -m pip list
# 仅列出用户明确安装的顶层包(自动过滤被作为底层依赖引入的包)
python -m pip list --not-required
# 以冻结格式输出(常用于生成 requirements.txt)
python -m pip list --format=freeze
从输出可以看出,输出结果中仅包含纯净的第三方库,哪怕标准库中存在与网络请求相关的 urllib,pip list 也绝不会将其输出,读者无需编写任何额外的过滤正则。
三、在 Python 代码内部获取:基于 importlib.metadata 正向提取
在很多开发场景下,我们需要在 Python 脚本程序内部(例如编写环境自检工具、插件注册系统或合规审计脚本)动态获取当前环境中安装的所有第三方包。如果此时去调用系统命令解析子进程输出,既繁重又容易产生跨平台兼容问题。
在 Python 3.8 及以上版本中,官方标准库内置了 importlib.metadata 模块。该模块严格遵循现代 Python 打包标准(PEP 566 / PEP 592),直接提供对已安装分发包元数据的程序化访问入口。
1. 完整实现代码
import importlib.metadata
def get_installed_third_party_packages():
"""
获取当前运行环境中所有安装的第三方扩展包名称与版本号
天然过滤系统内置 C 模块与官方标准库
"""
distributions = importlib.metadata.distributions()
installed = []
for dist in distributions:
name = dist.metadata["Name"]
version = dist.version
installed.append({"name": name, "version": version})
# 按名称字母顺序排序输出
installed.sort(key=lambda item: item["name"].lower())
return installed
if __name__ == "__main__":
packages = get_installed_third_party_packages()
print(f"共发现 {len(packages)} 个已安装的第三方扩展包:")
for pkg in packages:
print(f" • {pkg['name']} (v{pkg['version']})")
2. 为什么该方法能做到 100% 免疫系统内置模块?
正如图 2 所展示的四步流水线:importlib.metadata.distributions() 的底层执行逻辑是直接扫描 sys.path 中带有 .dist-info 或 .egg-info 后缀的目录结构。系统内置模块(如 sys、builtins)是纯 C 语言对象,在文件系统中连 .py 都不存在,更没有任何分发元数据;而标准库(如 math、os)虽然在文件系统中有源码,但官方并不会为其生成单包分发描述文件。因此,该方法在执行源头就完全绕开了系统内置和标准库,时间复杂度仅取决于 site-packages 下安装包的数量,性能优异且结果绝对可靠。
四、动态模块扫描场景:反向过滤集合差集法
除了正向查询已安装的分发包外,某些特殊架构(如动态加载当前进程已载入的所有模块、或者使用 pkgutil.iter_modules() 检索全局可导入项)会直接拿到全量混合列表。在这种必须做“反向剔除”的场景下,怎么过滤才不会漏判?
1. Python 3.10+ 原生标配:sys.stdlib_module_names 集合
在早期 Python 版本中,开发者往往需要自己维护一份标准库模块名称的文本文件,或者通过文件路径是否包含 site-packages 来勉强区分,极易误判。为了彻底解决这一痛点,Python 3.10 正式引入了 sys.stdlib_module_names。
sys.stdlib_module_names 是一个由 Python 解释器在构建时冻结生成的不可变集合(frozenset),其中完整记录了当前 Python 版本包含的所有官方标准库顶级模块名称(包括标准库中由 C 实现的底层扩展,如 _thread、_ssl 等)。与此同时,系统内置模块全集由 sys.builtin_module_names 元组维护。将两者结合,即可对全量模块名称进行 $O(1)$ 时间复杂度的集合差集运算。
import sys
import pkgutil
def filter_out_system_modules(module_names):
"""
接收模块名称迭代器,利用集合差集彻底过滤系统内置与标准库模块
适用于 Python 3.10 及以上版本
"""
# 汇总系统模块黑名单:标准库全集 + C 核心内置元组
system_modules = set(sys.stdlib_module_names) | set(sys.builtin_module_names)
# 集合差集过滤
third_party_names = set(module_names) - system_modules
return sorted(list(third_party_names))
if __name__ == "__main__":
# 获取系统当前全部可发现的一级模块
all_discovered = [mod.name for mod in pkgutil.iter_modules()]
third_party = filter_out_system_modules(all_discovered)
print(f"全量发现模块数: {len(all_discovered)}")
print(f"过滤后第三方模块数: {len(third_party)}")
print("第三方模块样例:", third_party[:5])
2. 低版本兼容方案(Python 3.7 - 3.9):物理路径判断法
如果项目必须运行在 Python 3.9 或更早的解释器上,此时 sys.stdlib_module_names 尚未诞生。最可靠的过滤策略是通过标准库 sysconfig 提取标准库与第三方库的物理根路径,结合模块对象的 __file__ 属性进行路径边界断言:
import sys
import sysconfig
import importlib
def is_third_party_module_legacy(module_name):
"""
适用于 Python 3.9 及以下的路径判定法
"""
# 1. 过滤 C 内置核心模块(无文件路径)
if module_name in sys.builtin_module_names:
return False
try:
mod = importlib.import_module(module_name)
except Exception:
return False
# 2. 内置或 C 扩展可能无 __file__ 属性
mod_file = getattr(mod, "__file__", None)
if not mod_file:
return False
# 3. 获取标准库物理路径
stdlib_path = sysconfig.get_path("stdlib")
site_packages = sysconfig.get_path("purelib")
# 若文件路径落在 site-packages 内,或不位于标准库路径,则确认为第三方
return (site_packages in mod_file) or (stdlib_path not in mod_file)
五、避坑提醒:容易引发异常与混淆的关键细节
__file__ 会抛出 AttributeError许多开发者尝试使用
module.__file__ 来判断包位置,但对于 sys、builtins、gc 等内置模块,由于其直接固化在二进制中,该属性根本不存在。代码中若未加 hasattr(m, '__file__') 或 getattr(m, '__file__', None) 保护,会直接导致程序崩溃。
help('modules')help('modules') 底层调用的是 pydoc 模块。在遍历查找过程中,为了获取 docstring,它可能会触发某些具有顶层执行代码的模块导入(Import Side-Effects)。这不仅带来巨大的 I/O 与内存开销,还可能意外执行模块内部的初始化连接逻辑。
通过
pip list 或 importlib.metadata 获取到的是分发包名(例如 PyYAML、Pillow、scikit-learn);而在代码中执行 import 时使用的是导入模块名(例如 yaml、PIL、sklearn)。如果你的任务是校验 requirements.txt,必须以分发包名为准;如果是校验代码中的 import 语句,则应注意两者之间的映射差异。
六、方案全景选型与总结
为了便于在实际开发与运维工作中快速抉择,下表对各场景下的解决方案进行了综合对比:
| 解决方案 | 调用环境 | 执行机制 | 标准库/内置过滤能力 | 推荐使用场景 |
|---|---|---|---|---|
| pip list | 系统终端 / CLI | 扫描 site-packages 元数据 | 天然 100% 隔离 | 命令行查看、依赖清单导出、环境审计 |
| importlib.metadata | Python 3.8+ 脚本 | 读取 PEP 566 分发描述符 | 源头 100% 隔离 | 代码内获取已安装包、版本比对、插件管理 |
| sys.stdlib_module_names 差集 | Python 3.10+ 脚本 | 集合数学差集运算 | 精准集合过滤 | 动态模块扫描、内存已加载模块清洗 |
| sysconfig 路径比对 | Python < 3.10 脚本 | 检查 __file__ 物理目录 | 路径匹配过滤 | 旧版本系统兼容与遗留脚本维护 |
总结而言,想要在 Python 查看已安装包时过滤系统内置模块,最高效优雅的手段永远是优先选择正向元数据检索机制:终端下用 python -m pip list,代码中用 importlib.metadata.distributions()。只有在需要对全局动态发现模块或运行时导入模块做清洗时,才需要借助 Python 3.10+ 的 sys.stdlib_module_names 与 sys.builtin_module_names 执行反向集合差集。遵循这一路径,就能以最低的代码复杂度,彻底杜绝内置与标准库模块的干扰。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









