先聊一个很常见的场景:你写了一个Python项目,准备部署到服务器或者交给同事,需要一份依赖清单。很多人第一反应是 pip freeze > requirements.txt,但这样做往往会埋下隐患。更可靠的做法,其实是换用 pipreqs。

pipreqs 生成 requirements.txt 为什么比 pip freeze 更靠谱
简单来说,pipreqs 的核心理念就是——"只扫自己门前的雪"。它只盯着项目源码里那些实实在在用了 import 语句引入的包,绝不会把整个Python环境里装了一堆但你压根没用的包也塞进清单里。
而 pip freeze 则是典型的"一锅端"。它把当前虚拟环境(甚至全局环境)里所有已安装的包一股脑儿全导出来,不管这些包是项目真正需要的,还是你为了某个调试小工具临时装的。问题就出在这里:等你真正部署上线,或者把项目交接给同事时,经常会出现"这个包根本用不上却报错""那个必需的包又因为冲突卡住"的尴尬情况。这不是代码写错了,而是依赖清单里混了太多"无关人士"。
适用场景很清晰:新项目初始化、把代码交给别人、准备 Docker 镜像构建、提交代码前自查依赖。但也要注意,它不擅长处理那些纯粹的开发依赖,比如 pytest、black 这类工具——默认情况下是会被忽略的,除非你加了额外参数。另外,pipreqs 不解析 setup.py 或 pyproject.toml,它的眼里只有 Python 源文件里实实在在写着的 import 语句。
安装和基础命令怎么跑起来
安装没什么复杂的,但有个细节值得留意:确保安装的是最新版,避免老版本那种路径相关的 bug。直接跑一句:
pip install --upgrade pipreqs
然后,进到你的项目根目录(就是 src/ 目录或者 app.py 等源码所在的那一层),执行:
pipreqs ./
它就会自动扫描目录下所有 .py 文件,然后生成一份清爽的 requirements.txt。如果运行时报错说找不到 requirements.txt,那是因为文件已存在,且被锁住了——删掉再跑一次,或者加个 --force 参数强制覆盖就行。
几个实用的小参数值得记住:
- --encoding=utf-8:如果你的项目里夹杂了中文注释或路径,不加这个很可能会遇到
UnicodeDecodeError,加它就对了。 - --diff requirements.txt:想增量更新?用它对比现有的依赖文件,只输出新增的包,适合在持续迭代中精细管理。
- --sa vepath ./reqs-dev.txt:如果你不想覆盖主文件,比如想把开发依赖单独放一份,就用这个指定输出路径。
怎么让 pipreqs 扫描子目录和识别动态导入
默认情况下,pipreqs 只扫描当前目录及其直接的子目录。如果你的项目结构是 src/mylib/、apps/api/ 这种多层嵌套,就可能漏掉关键依赖。这时候需要显式告诉它该去哪:
pipreqs ./src ./apps --force
但必须承认,pipreqs 在动态导入面前确实无能为力。比如你在代码里用了 __import__(f"{name}_plugin") 或者 importlib.import_module() 这样的字符串拼接式导入,它是完全"看不懂"的。这类依赖,只能老老实实手动补进 requirements.txt。
一些常见的坑点再强调一下:
- Django 项目里,你在
INSTALLED_APPS中配置的第三方 app(比如django-crispy-forms),pipreqs并不会自动识别过去。为什么?因为它只认import语句,不解析配置文件。这类依赖必须你人工核对添加。 - 如果你用了
setuptools的entry_points或插件机制,同样没戏。 - 一个实用的建议:执行完
pipreqs后,不妨再用grep -r "import\|from.*import" . --include="*.py"快速扫一遍,看看有没有可疑或遗漏的模块名。
生成结果不准?先检查 import 语句写法
pipreqs 的工作原理决定了它依赖的是"字面量"分析,而不是代码真正的运行时行为。所以某些写法会导致结果"对不上号":
- 能识别的:
import requests as req没问题,它会认出requests;import mypkg.utils也能认出mypkg。但要注意,它不会深挖mypkg内部又依赖了哪些包。 - 没问题的:相对导入比如
from . import config或from ..models import User,只是影响模块查找,不会引入新的第三方包,所以不会有问题。 - 容易被误解的:像
try: import ujson except ImportError: import json这种写法,pipreqs会把ujson也识别为依赖,哪怕实际运行走的可能是json。它不管运行时的逻辑分支,只认代码里写着的import。
真正容易让 pipreqs "失手"的,其实是那些条件导入加上第三方包的组合。举个例子:
if sys.version_info >= (3, 9):
from importlib import resources
else:
import importlib_resources as resources
这种情况下,pipreqs 会忠实地把 importlib_resources 也列进清单。但问题在于,如果你的运行环境已经是 Python 3.9 以上,importlib_resources 这个包根本不会生效。这时候就得你动动手指,把实际没用的那个依赖删掉。