pip freeze 导出所有已安装包(含传递依赖),是环境快照而非最小依赖清单;应配合虚拟环境使用,并手动或用 grep/select-string 过滤 pip、setuptools 等冗余包。

pip freeze 为什么导出的包比实际用的多
pip freeze 会列出当前环境中所有已安装的包(包括依赖的依赖),而不是只导出项目直接声明的依赖。比如你只装了 requests,但它依赖 urllib3、chardet 等,这些也会被一并导出。
这不是 bug,是设计如此——pip freeze 的定位是「环境快照」,不是「最小依赖清单」。
- 如果你用的是虚拟环境(强烈建议),那至少能排除系统级包干扰
- 如果混用全局环境,
pip freeze很可能包含 IDE 插件、linter、甚至你某次临时测试装的包 - 某些包(如
pip、setuptools、wheel)默认也会出现,一般不需要写进requirements.txt
怎么过滤掉不该进 requirements.txt 的包
最常用且安全的做法是手动剔除,但可以借助 grep -v 快速排除:
pip freeze | grep -v "^\(pip\|setuptools\|wheel\|pkg-resources\)" > requirements.txt
Windows 用户可用 PowerShell 替代:
pip freeze | Select-String -Pattern "^(pip|setuptools|wheel|pkg-resources)" -NotMatch > requirements.txt
- 正则里列出的包名必须加
^锚定行首,避免误删包名含子串的(比如误删pip-tools) -
pkg-resources是旧版 setuptools 带来的常见冗余项,出现在某些 Docker 镜像或 Ubuntu 系统中 - 别盲目加
-i忽略大小写——有些包名就是大小写敏感的(如PyYAML)
pipreqs 和 pip freeze 的核心区别在哪
pipreqs 是基于项目源码静态分析 import 语句来推测依赖,而 pip freeze 是基于已安装包列表。两者逻辑完全不同:
-
pipreqs ./myproject --encoding=utf-8只生成代码里真实 import 过的包(不含未使用的依赖) - 但
pipreqs无法识别动态导入(如importlib.import_module)、字符串拼接导入、或条件导入的包 - 它也看不到
setup.py或pyproject.toml里声明的构建/测试依赖 - 若项目有多个入口(如 CLI + Web API),需确保扫描路径覆盖全部
.py文件
所以更稳妥的做法是:先用 pipreqs 生成初稿,再用 pip freeze 核对版本是否一致,最后人工补全缺失项(比如 click 被 pipreqs 漏掉,但运行时报错找不到)。
导出时要不要带版本号?什么时候该用 == 而不是 >=
默认 pip freeze 输出的是 == 版本锁定,这是生产环境推荐做法——保证重装时完全复现相同行为。
- 开发阶段若想允许小版本升级(如修复 CVE),可手动把
==改成~>(兼容性升级)或>=(谨慎使用) - 不要在
requirements.txt里混用==和>=,容易导致 pip 解析冲突或降级失败 - 若依赖中存在本地路径包(
-e ./src/mypackage)或 Git 仓库(git+https://...),pip freeze会原样保留,但这类条目不能被普通pip install -r直接复现,需额外处理
真正容易被忽略的是:不同 Python 版本下 pip freeze 可能输出不同包(比如 importlib-metadata 在 Python 3.8+ 是内置模块,但旧环境仍需安装)。导出前务必确认目标部署环境的 Python 版本。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











