AI摘要
🧱 一、现实问题:requirements.txt 的“依赖泥潭”
在老项目中,常见的现象是这样的:
$ pip list
Package Version
--------------- --------
numpy 1.24.3
pandas 2.0.1
requests 2.31.0
urllib3 2.0.4
charset-normalizer 3.2.0
idna 3.4
fastapi 0.95.2
uvicorn 0.22.0
pydantic 1.10.11
...看起来一切正常,其实乱得很。
因为 requirements.txt 里可能只有 3~5 个包:
fastapi==0.95.2
pandas==2.0.1
uvicorn==0.22.0但 pip list 却列出了几十个依赖。这时有两个关键问题:
- 哪些是项目自己需要的?
- 哪些是依赖的依赖(transitive dependencies)?
我们要在迁移前先把这些关系理清,否则迁移到 pyproject.toml 之后仍旧是混乱的。
🧩 二、精准识别“主依赖” vs “传递依赖”
1️⃣ 使用 pipdeptree 分析依赖树
安装:
pip install pipdeptree查看依赖层级:
pipdeptree --warn silence示例输出:
fastapi==0.95.2
- pydantic [required: <2.0.0, installed: 1.10.11]
- starlette [required: <1.0.0, installed: 0.27.0]
pandas==2.0.1
- numpy [required: >=1.20.3, installed: 1.24.3]
- python-dateutil [installed: 2.8.2]
- pytz [installed: 2023.3]
uvicorn==0.22.0
- click [installed: 8.1.3]
- h11 [installed: 0.14.0]👉 很明显,fastapi、pandas、uvicorn 是你主动安装的;
其余如 pydantic、numpy、starlette 等都是“传递依赖”。
Tip: 你可以用 pipdeptree --reverse 反查:哪个包依赖了这个库。🧮 三、清理并锁定顶级依赖
1️⃣ 提取顶级包
pip freeze > all.txt
pipdeptree --warn silence --freeze > tree.txt打开 tree.txt,手动(或脚本)过滤掉那些被其他包引用的依赖。
最终保留的核心依赖(即顶级包)写入新的 requirements.txt:
fastapi==0.95.2
pandas==2.0.1
uvicorn==0.22.02️⃣ 验证清理是否正确
先创建个全新虚拟环境:
python -m venv cleanenv
source cleanenv/bin/activate
pip install -r requirements.txt
pipdeptree如果依赖树和原项目行为一致,你的“核心依赖集”就整理对了。
🧭 四、迁移到 pyproject.toml + uv
安装 uv(强烈推荐):
curl -LsSf https://astral.sh/uv/install.sh | sh初始化:
uv init导入依赖:
uv add --from-requirements requirements.txt查看结果(生成的 pyproject.toml):
[project]
name = "movie-analyzer"
version = "0.1.0"
dependencies = [
"fastapi==0.95.2",
"pandas==2.0.1",
"uvicorn==0.22.0",
]
requires-python = ">=3.10"🧰 五、验证和同步依赖
锁定依赖版本:
uv lock安装依赖:
uv sync验证运行:
uv run python main.py✅uv会自动生成.venv和uv.lock,确保依赖一致性。
即使协作者在另一台机器,也能完美复现同样的环境。
🧱 六、用 Docker 打包部署
这是最通用、最干净的构建方式。
FROM python:3.11-slim AS base
# 安装 uv
RUN pip install uv
WORKDIR /app
# 拷贝依赖文件
COPY pyproject.toml uv.lock ./
# 安装依赖
RUN uv sync --frozen --no-cache
# 拷贝源码
COPY src ./src
# 入口命令
CMD ["uv", "run", "fastapi", "run", "--host", "0.0.0.0", "--port", "8000"]构建镜像:
docker build -t docker-test .运行容器:
docker run -p 8000:8000 docker-test🔍 七、附:自动识别“顶级依赖”的脚本
如果你懒得手动筛选,可以用这个 Python 脚本快速过滤:
import subprocess
def get_top_level_packages():
output = subprocess.check_output(["pipdeptree", "--warn", "silence"]).decode()
top_level = []
for line in output.splitlines():
if not line.startswith(" "): # 顶级包
pkg = line.split("==")[0]
top_level.append(pkg)
return top_level
if __name__ == "__main__":
for pkg in get_top_level_packages():
print(pkg)执行:
python find_top_deps.py > clean_requirements.txt它会自动生成仅包含“主依赖”的 requirements.txt。
⚡ 八、实战总结
| 步骤 | 工具 | 作用 |
|---|---|---|
| 依赖分析 | pipdeptree | 查出依赖关系树 |
| 顶级提取 | 脚本 or 手动 | 确定哪些是核心依赖 |
| 环境验证 | venv+pip install | 确认依赖可重现 |
| 现代化迁移 | uv init + add | 生成 pyproject.toml |
| 锁定同步 | uv lock + sync | 可重复依赖管理 |
| 部署打包 | Docker + uv | 一致性环境交付 |
🧠 九、总结
“requirements.txt 是笔记本,而 pyproject.toml 是配置中心。”
通过 pipdeptree 清理依赖、再用 uv 统一管理,
你能把老项目彻底从“依赖泥潭”里拔出来,
同时获得更快的安装、更好的团队协作与更稳定的部署。
Caleb https://reinness.com/posts/313 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自小陈博客 !