deptry 是目前唯一能同时可靠识别未声明包和未使用依赖的静态工具,但对 importlib 动态导入、类型提示引用、插件式加载等隐藏依赖无能为力,需结合 coverage.py、strace 等动态手段验证。

直接结论:deptry 是目前唯一能同时可靠识别「未声明包」和「未使用依赖」的静态工具,但对隐藏依赖(如 importlib 动态导入、类型提示引用、插件式加载)基本无能为力——这类问题必须靠动态手段补位。
deptry 能抓什么、不能抓什么
它只做三件事:读 pyproject.toml 或 requirements.txt,扫描所有 .py 文件里的顶层 import 和 from ... import,再比对差异。所以:
- ✅ 能精准报出
unused_dependency: pandas(声明了但没 import)或missing_dependency: typer(代码里写了import typer但没声明) - ❌ 不处理
importlib.import_module("click")、__import__(name)这类动态导入 - ❌ 默认跳过
tests/目录,不加--scan-test-files就不会检查测试代码里的引用 - ❌ 类型提示里的包(如
def f(x: numpy.ndarray):)不被视为“使用”,除非加--include-optional-deps
隐藏依赖必须用动态方式验证
静态分析天然漏掉运行时才确定的依赖。真正要确认一个包是否被隐式调用,得让它跑起来:
安全 Python 代码沙箱:在隔离的沙箱中执行任意 Python 代码,预装 requests、NumPy、pandas 库。返回 stdout、stderr。
- 用
coverage.py+pytest跑全量测试,生成覆盖率报告,再查哪些包的模块从未被执行过 ——coverage run -m pytest && coverage report -m - 启动项目主进程(如 Flask/FastAPI 启动、CLI 命令执行),同时用
strace(Linux/macOS)或Procmon(Windows)捕获实际打开的.py文件路径,反向推导加载了哪些包 - 对疑似隐藏依赖的包,手动在 Python REPL 里执行
import pkgutil; [name for _, name, _ in pkgutil.iter_modules() if 'xxx' in name]看是否被其他已安装包间接引入
常见误判场景与绕过技巧
很多“未使用”报警其实是假阳性,删之前务必交叉验证:
立即学习“Python免费学习笔记(深入)”;
- CI 脚本里硬编码调用的包(比如
black --check),deptry 扫不到 shell 脚本,得人工核对.github/workflows/或Makefile - 打包工具(如 PyInstaller)需要的
--hidden-import包,这些在源码里不出现,但必须保留在requirements.txt中 - 使用 Poetry 的项目,若没加
--project-type poetry,deptry 会完全忽略[tool.poetry.dependencies]段落 - 子模块路径没显式传入(如项目结构是
src/myapp/),只跑deptry .会漏扫,必须写成deptry src/ --requirements-txt pyproject.toml
真正清理依赖不是看 deptry 报什么就删什么。它给的是线索,不是判决书。最容易被忽略的是:类型提示、测试 fixture、构建脚本、打包配置、甚至 IDE 插件依赖——这些地方的包,既不在 import 里,也不在运行时加载链上,但删了项目可能立刻崩。

















