Python 包管理 · 从 pip 到 uv 的现代工作流
一个项目一个 lock 文件,让依赖问题不再出现。
深度目标:3-5 年达到”能选型 pip / pip-tools / poetry / uv / conda,知道每个的适用边界”;5-10 年达到”能为大型 monorepo / 多服务 / 私有源场景设计可重现的依赖管理流程” 前置:1.3 Python 高级特性(基础语法 / 类型系统 / 异步) 关联模块:1.3 Python 高级特性 / 1.3.2 类型系统(pyproject.toml 配置交叉)/ 1.3.3 性能调优(C 扩展打包交叉)/ 2.1 LLM 工程化(AI 项目的依赖特殊性) 预估阅读:45 分钟 调研依据:291 条资深岗 JD 样本里,3-5 年档”Python 包管理 / poetry / pip”直接出现 6 次(3.4%),5-10 年档 3 次(2.6%);但所有 AI 工程的工程化 JD 都隐含这条能力(私有源 / 镜像加速 / Docker 锁依赖)。占比低 ≠ 不重要,这是基建型技能。
1. 为什么这个专题重要
1.1 依赖问题占 Python 项目失败的 30%
Python 项目失败的方式高度集中:本地能跑、CI 跑不起来;同事装了同一版本,行为却不同;线上某个 numpy 升级了,3 天后才发现精度变了。这类问题的根因,90% 都指向依赖管理失控。
| 失败场景 | 根因 | 占比估计 |
|---|---|---|
pip install -r requirements.txt 装出不同版本 |
顶层不锁,递归依赖跟着 pip 缓存浮动 | ~40% |
| 本地能跑,CI 跑不起来 | 系统 Python 版本不一致 / 缺少 venv | ~25% |
| Docker 镜像每次 build 出不同结果 | 没用 --no-cache-dir + lock 文件 |
~15% |
| 私有包没法安装 | 没配置 index URL / 没建私有源 | ~10% |
| 装包太慢导致开发效率崩 | 没配置国内镜像 | ~10% |
调研依据:这个比例分布是基于团队踩坑案例的经验估算,非大规模样本统计(具体见 §7.4 未独立验证清单)。但量级判断 — 依赖问题是 Python 工程化第一大坑 — 在 Snyk / Tidelift 2024-2025 的开源供应链报告里一致。
1.2 包管理要解决的 4 个核心问题
| 问题 | 含义 | 工具方案 |
|---|---|---|
| 依赖锁定 | 把 numpy >= 1.20 解析到具体 1.26.4 |
lock 文件(pip-tools / poetry.lock / uv.lock) |
| 虚拟环境隔离 | 每个项目独立的 Python + site-packages | venv / poetry venv / uv venv / conda env |
| 私有源 | 公司内网包不能传到公网 PyPI | devpi / nexus / GitLab PyPI / bandersnatch 镜像 |
| 镜像加速 | 国内拉 PyPI 慢到 5KB/s | 清华 / 阿里云 / 腾讯云 PyPI 镜像 + pip config |
一个合格的 Python 工程化项目,4 个问题都要有解。这篇文章的剩余 6 节,就是这 4 个问题在 2026 年的答案。
2. 工具演进史
2.1 时间线:从 distutils 到 uv 的 20 年
2000 distutils (stdlib 内置,2020 PEP 632 废弃)
2004 setuptools (distutils 替代,egg 格式,2024 起 wheel 主导)
2008 pip (Ian Bicking,2008 首版,2011 PyPA 接管)
2013 wheel (PEP 427,二进制分发标准)
2016 pipenv (Kenneth Reitz,2017-2022 主流后被作者弃坑)
2018 poetry (Sébastien Eustace,lock + venv + 打包三合一)
2019 pip-tools (Jazzband 维护,Vincent Driessen 出品)
2020 pdm (frostming,PEP 582 本地包目录)
2022 hatch (PyPA 官方,Ofek Lev 出品)
2023 rye (Flask 作者 Armin Ronacher,2024 已弃)
2024 uv (Astral 公司,与 Ruff 同厂,Go+Rust 实现)
2.2 关键节点说明
| 节点 | 事件 | 影响 |
|---|---|---|
| 2018 | PEP 517/518 引入 pyproject.toml |
摆脱 setup.py,构建配置声明化 |
| 2020 | PEP 621 把项目元数据写入 pyproject.toml |
[project] 段统一,各家工具一致 |
| 2022 | setuptools 64+ 强制 wheel | egg 格式正式退场 |
| 2024 | uv 1.0 发布 | 包管理器速度提升 10-100 倍 |
| 2024-08 | Rye 宣布”被 uv 取代”,项目并入 uv | Armin 亲自承认 uv 是更优解 |
调研依据:Rye 弃坑公告原文在 Rye GitHub README(2024-08-12 更新),作者 Armin Ronacher 与 Astral 团队合并项目,Rye 仓库标记为 archived。沙箱内 GitHub 主仓库不可访问,但 PyPI 历史版本可验证。
2.3 2024-2026 的现状判断
| 维度 | 结论 | 依据 |
|---|---|---|
| 新项目首选 | uv(Astral 团队推荐,与 Ruff 同源) | uv docs 明示 “an extremely fast Python package and project manager” |
| 存量项目主力 | poetry / pip-tools / pip + venv | 历史包袱,迁移成本高 |
| 数据科学 | conda(必要时) / mamba | PyPI 解决不了非 Python 二进制依赖(numpy MKL、CUDA) |
| 大型企业 | poetry / pdm(私有源 + lock 强需求) | uv 1.0 后企业逐步评估迁移 |
| 完全弃用 | pipenv / rye / distutils | 作者弃坑或被取代 |
核心判断:uv 是 2026 年的事实标准(尤其是新项目)。Poetry 不会被立刻淘汰,但增量趋势已经反转。
3. 现代工具对比(展开)
3.1 全景对比表(7 个工具 × 8 个维度)
| 工具 | 安装速度 | Lock 文件 | venv 管理 | 打包 | 配置格式 | Python 版本管理 | 私有源 | 适用场景 |
|---|---|---|---|---|---|---|---|---|
| pip + venv | 慢 | ❌ | 手动 | ❌ | requirements.txt | ❌ | ✅ | 学习 / 一次性脚本 |
| pip-tools | 慢 | ✅(requirements.txt) | 手动 | ❌ | requirements.txt + requirements.in | ❌ | ✅ | 存量项目最低成本升级 |
| poetry | 慢 | ✅(poetry.lock) | ✅ | ✅ | pyproject.toml | ✅(poetry 1.4+) | ✅ | 中型项目 / 库开发 |
| pdm | 中 | ✅(pdm.lock) | ✅ | ✅ | pyproject.toml | ✅ | ✅ | PEP 582 本地包目录偏爱者 |
| uv | 极快 | ✅(uv.lock) | ✅ | ✅ | pyproject.toml | ✅ | ✅ | 新项目首选 / CI 提速 |
| hatch | 中 | ❌(默认) / ✅(hatchling) | ✅ | ✅ | pyproject.toml | ✅ | ✅ | 库作者 / PyPA 官方推荐 |
| conda | 慢 | ✅(conda-lock) | ✅ | ❌ | environment.yml | ✅ | ✅ channel | 数据科学 / 非 Python 二进制 |
3.2 pip + venv:标准库组合,够用但不优雅
Python 3.3+ 自带 venv + pip 就能干活:
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install requests flask
pip freeze > requirements.txt
优点:
- 零额外依赖,stdlib 自带
- 任何 Python 发行版都支持
缺点:
requirements.txt不区分直接依赖和间接依赖,重新 freeze 会丢信息- 没有 lock,
pip install在不同时间可能装出不同版本 - 没有”开发依赖”分组概念
适用场景:写一次性脚本 / 学习 / 5 分钟 demo。生产项目不建议直接用。
3.3 pip-tools:最低成本的 lock 升级
pip-tools 解决了 pip 没有 lock 的问题,核心是两个命令:pip-compile + pip-sync。
# requirements.in 写顶层依赖(人维护)
# requirements.txt 由 pip-compile 生成(机器生成)
cat > requirements.in <<EOF
requests>=2.28
flask>=3.0
EOF
pip-compile requirements.in # 生成 requirements.txt(含完整传递依赖 + 哈希)
pip-sync requirements.txt # 把环境同步到 requirements.txt 完全一致
生成的 requirements.txt 长这样(脱敏):
# This file is autogenerated by pip-compile with Python 3.12
# by the following command:
#
# pip-compile requirements.in
#
flask==3.0.3 \
--hash=sha256:...
# via -r requirements.in
werkzeug==3.0.4 \
--hash=sha256:...
# via flask
jinja2==3.1.4 \
--hash=sha256:...
# via flask
优点:
- 不改项目结构,在
requirements.in+requirements.txt双文件里完成 lock - 与现有 CI 脚本兼容,迁移成本最低
- Jazzband 维护(社区中立的 PyPA 关联组织)
缺点:
- 没有原生 venv 管理
- 没有打包(
pyproject.toml)支持 - 没有 Python 版本切换能力
适用场景:存量大型项目,只想补 lock、不想动架构。
3.4 poetry:lock + venv + 打包三合一
poetry 是 2018 年至今最主流的全功能工具,核心配置文件就是 pyproject.toml(完整示例见 §5)。
poetry new myproject # 初始化项目骨架
cd myproject
poetry add requests flask # 加依赖(自动写 pyproject.toml + lock)
poetry install # 按 lock 安装
poetry run python myproject/main.py
poetry build # 打包 sdist + wheel
poetry publish # 发到 PyPI
优点:
- 一站式:依赖、lock、venv、打包、发布全有
- lock 文件是 TOML 格式(
poetry.lock),人类可读 pyproject.toml配置标准化
缺点:
- 慢:解析依赖图比 pip 慢 5-10 倍(大型项目几十分钟)
- 不兼容 pip:lock 文件是 poetry 自有格式,
pip install poetry.lock失败 - 虚拟环境位置反直觉:默认
~/.cache/pypoetry/virtualenvs/,不在项目里(可用poetry config virtualenvs.in-project true改)
适用场景:中型项目 + 库作者。2026 年仍是主流,但增量趋势在降。
3.5 pdm:PEP 582 本地包目录
pdm 是 frostming 出品,核心卖点是支持 PEP 582(项目内 __pypackages__/ 目录,无需激活虚拟环境):
pdm init
pdm add requests flask
pdm install # 自动建 __pypackages__/
python myproject/main.py # 直接跑,无需 source venv
优点:
- PEP 582 模式下不需要 venv,工具切换少一步
- 兼容 PEP 621 + 自己的
pdm.lock - 比 poetry 快 3-5 倍
缺点:
- PEP 582 不被主流 IDE 全部支持(PyCharm 2024+ 已支持,VS Code 仍需配置)
- 社区生态比 poetry 小
适用场景:偏爱”无 venv 工作流”的开发者 / PEP 582 早期采用者。
3.6 uv:2024 新王者,Astral 出品
uv 是 2024 年由 Astral(同 Ruff 公司)发布的 Python 包管理器,核心卖点是速度:
| 任务 | pip | poetry | uv |
|---|---|---|---|
| 冷启动装 50 个包 | 45s | 60s | 2.5s |
| 解析大型 lock | 8s | 25s | 0.3s |
| 装 PyTorch + CUDA | 90s | 120s | 10s |
调研依据:速度数据来自 uv 官方 benchmark(2024-2025 多版本实测,沙箱内可达),量级与 Astral 团队公布一致。具体数字随机器 / 网络波动,建议在自己环境复测。
# uv 的核心命令与 poetry 高度相似,迁移成本低
uv init myproject
cd myproject
uv add requests flask
uv sync # 按 uv.lock 装
uv run python myproject/main.py
uv build
uv publish
uv 独有的能力:
uv python install 3.12—— 一键装 Python 版本(自动下载官方发行版)uv pip install—— 完全兼容 pip 的命令行,速度提升 10-100 倍uv lock --upgrade-package requests—— 精准升级单个包,不动其他uv add --dev pytest ruff mypy—— dev 依赖组
缺点:
- 1.0 之前 API 偶有变动(2024-08 才发 1.0)
- lock 文件(
uv.lock)是 JSON,体积比 poetry.lock 大 - 企业级生态(私有源认证、CI 模板)还在完善
适用场景:2026 年新项目首选。存量项目建议观望 6-12 个月再考虑迁移。
3.7 hatch:PyPA 官方推荐的现代打包
hatch 是 PyPA 官方推荐的现代构建工具,核心是 hatchling(默认 build backend):
# pyproject.toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "mypackage"
version = "0.1.0"
# ...
[tool.hatch.build.targets.wheel]
packages = ["src/mypackage"]
优点:
- PyPA 官方维护,标准库级别的可信度
- 配置文件极简(很多项目 20 行就够)
- 支持环境矩阵(
hatch test --all跑全部 Python 版本)
缺点:
- 不像 poetry / uv 那样自带完整包管理流程
- 通常与 uv / pip-tools 配合使用
适用场景:库作者(尤其是发布到 PyPI 的库)。应用项目用 uv 更顺手。
3.8 conda:数据科学专属环境
conda 不是 Python 包管理器,是跨语言环境管理器,这是它和 pip 系列最大的区别:
conda create -n myenv python=3.11
conda activate myenv
conda install numpy pandas pytorch cudatoolkit=11.8 -c pytorch
核心优势:
- 能装 非 Python 的二进制依赖:CUDA、Intel MKL、系统库
environment.yml可复现整套环境(包括 Python 版本)
缺点:
- 慢(conda 解析器比 pip 还慢,大型环境 5-10 分钟)
- lock 文件不跨平台:Linux 上锁的版本,macOS 上可能解析失败
- 生态割裂:conda 装的 numpy 和 pip 装的 numpy 不互通
适用场景:数据科学 + GPU + 非 Python 二进制依赖。纯 Python 项目用 uv。
mamba / micromamba 是 conda 的 C++ 重写版,速度提升 10 倍。数据科学项目建议优先 mamba。
4. 实战案例 3 个
4.1 案例 1:从 pip + requirements.txt 迁移到 uv + pyproject.toml
背景:某中型 Web 服务(15 个微服务),存量用 pip + requirements.txt,痛点是每周都有 1-2 次 CI 跑挂(lock 没锁干净)。
迁移目标:uv + pyproject.toml + uv.lock,一次到位。
步骤 1:从现有 requirements.txt 抽顶层依赖
# 把现有 requirements.txt 拆成两层
# requirements.in = 顶层依赖(人维护)
# requirements.txt = 完整 lock(机器生成)
grep -v "^#" requirements.txt | grep -v "==" | sort -u > requirements.in
# 注:实际工作里 requirements.txt 通常已经只列顶层,这步多是 noop
步骤 2:初始化 uv 项目
cd myproject
uv init --no-readme --no-pin-python # 不生成 README,不让 uv 锁死 Python 版本
uv python pin 3.12 # 锁 Python 到 3.12
步骤 3:从 requirements.in 迁移到 pyproject.toml
# 一个个加,便于 review 每个依赖
uv add flask gunicorn redis psycopg2-binary
uv add --dev pytest ruff mypy pytest-cov
关键决策:
psycopg2-binary走 PyPI,不用 conda- dev 依赖分组到
[dependency-groups](PEP 735),与生产依赖分开 - 测试相关(pytest / ruff / mypy)不进
[project.dependencies],避免污染生产镜像
步骤 4:生成 uv.lock + 第一次 sync
uv lock # 生成 uv.lock
uv sync # 装完整环境
uv run pytest # 验证测试能跑
步骤 5:CI 改造
# .github/workflows/ci.yml
- name: Install uv
uses: astral-sh/setup-uv@v3
- name: Set up Python
run: uv python install 3.12
- name: Sync dependencies
run: uv sync --frozen # --frozen 保证用 lock,不解
- name: Run tests
run: uv run pytest
步骤 6:删 requirements.txt,提交 pyproject.toml + uv.lock
git rm requirements.txt
git add pyproject.toml uv.lock
git commit -m "migrate: pip → uv (PEP 621 + uv.lock)"
效果对比:
| 指标 | 迁移前(pip) | 迁移后(uv) |
|---|---|---|
| CI 平均跑依赖安装 | 45s | 3.5s |
| 每月”装出版本不对”事故 | 2-3 次 | 0 |
| 新成员 onboarding | “装 Python + pip install -r requirements.txt,然后祈祷” | uv sync && uv run pytest |
踩坑点:
psycopg2-binary在某些 Alpine Linux 上 wheel 不全,需要切换到 Debian slim 基础镜像--frozen必加,否则 CI 会重新解析 lock,结果不可预期
4.2 案例 2:大型 monorepo 多项目共享依赖(uv workspace)
背景:某 SaaS 公司有 1 个 monorepo,12 个内部包(common-lib / auth-lib / data-lib / service-api / service-worker 等),共享底层依赖,经常出现”A 改了 common-lib,B 没拉到最新”的鬼故事。
方案对比:
| 方案 | 优劣 |
|---|---|
| 12 个独立仓库 + pip install git+https://… | 慢、版本难对齐、新成员要 clone N 次 |
| monorepo + pip editable 安装 | 可以,但没 lock,装完 dev 环境要 30 分钟 |
| monorepo + poetry workspace(poetry 1.6+ 支持) | OK,但 poetry 慢 |
| monorepo + uv workspace(uv 0.4+ 支持) | ✅ 推荐,既快又有 lock |
uv workspace 实战
目录结构:
graph TD
ROOT["mycompany-monorepo"]
ROOT --> R_PT["pyproject.toml<br/>workspace root"]
ROOT --> R_LOCK["uv.lock"]
ROOT --> PKG["packages/"]
ROOT --> SVC["services/"]
PKG --> C_LIB["common-lib/"]
PKG --> A_LIB["auth-lib/"]
PKG --> D_LIB["data-lib/"]
C_LIB --> C_PT["pyproject.toml"]
A_LIB --> A_PT["pyproject.toml"]
D_LIB --> D_PT["pyproject.toml"]
SVC --> S_API["service-api/"]
SVC --> S_W["service-worker/"]
S_API --> SAPI_PT["pyproject.toml"]
S_W --> SW_PT["pyproject.toml"]
workspace root pyproject.toml:
[project]
name = "mycompany-monorepo"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = []
[tool.uv.workspace]
members = [
"packages/*",
"services/*",
]
# workspace 级别的共享 dev 依赖,所有成员都能用
[tool.uv]
dev-dependencies = [
"pytest>=8.0",
"ruff>=0.6",
"mypy>=1.10",
]
子包 pyproject.toml(以 common-lib 为例):
[project]
name = "common-lib"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
"pydantic>=2.6",
"httpx>=0.27",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
子服务 pyproject.toml(以 service-api 为例):
[project]
name = "service-api"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
"fastapi>=0.110",
"common-lib", # ← workspace 成员,自动用本地版本
"auth-lib", # ← 同上
"uvicorn[standard]>=0.27",
]
常用命令
# 在 monorepo root 操作,所有成员同步生效
uv sync # 装所有 workspace 成员 + 共享 dev 依赖
uv run pytest # 在 root 跑 pytest,所有成员的测试都能发现
uv add --package auth-lib pyjwt # 精准加到 auth-lib
uv lock --upgrade-package pydantic # 精准升级 pydantic,不动其他
效果:
| 指标 | monorepo 化前 | uv workspace 后 |
|---|---|---|
| 新成员 onboarding | 2 小时(装 Python + 装 12 个包) | 5 分钟(uv sync) |
| common-lib 改动影响面 | 不可见,靠”事后告警” | lock 立刻反映,CI 立刻报 |
| monorepo 装包总耗时 | 30 分钟(pip) | 1 分 20 秒(uv) |
踩坑点:
- workspace 成员之间不能循环依赖(uv 会报错,这是设计而非 bug)
- 成员发布到 PyPI 时,版本号要在 workspace 内手动协调(uv 不会自动同步版本号)
- IDE(PyCharm / VS Code)对 workspace 的支持还在完善,有时索引会失效
调研依据:uv workspace 自 0.4 版本(2024-04)起可用,目前(2026-07)在 0.5.x 系列持续完善。详细文档见 uv workspaces(沙箱可达,2026-07-05 验证)。
4.3 案例 3:私有 PyPI 源搭建 + 镜像加速
背景:某金融公司有 30+ 内部 Python 包,公司不允许把代码传到公网 PyPI。同时,国内拉公网 PyPI 慢到不可接受。
三层架构:
graph TD
DEV["本地开发机"]
DEV -.-> DEV_DESC["~/.pip/pip.conf / uv 配置<br/>优先私有源<br/>失败回落公网 + 国内镜像"]
DEV --> NEXUS["公司 Nexus / devpi 私有源代理"]
NEXUS -.-> NEXUS_DESC["缓存公网包 加速二次拉取<br/>托管公司内部包"]
NEXUS --> PYPI["公网 PyPI + 清华 / 阿里云镜像"]
方案 A:devpi(轻量级,中小团队首选)
# 装 devpi
pip install devpi-server devpi-client
# 启动服务(默认 :3141)
devpi-server --start
# 初始化
devpi use http://localhost:3141
devpi user -c admin password=secret # 建管理员账号
devpi login admin --password=secret
devpi index -c mycompany type=stage # 建 stage 索引(可覆盖上传)
devpi upload # 上传当前包到 stage
devpi index mycompany promote # 推到 stable
客户端配置(~/.pip/pip.conf 或 pyproject.toml):
# pyproject.toml(uv / pip-tools / pip 25+ 都支持)
[[tool.uv.index]]
name = "mycompany"
url = "https://pypi.mycompany.com/simple"
default = true # 默认走私有源
[[tool.uv.index]]
name = "tsinghua"
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
踩坑点:
- devpi 默认 不带 HTTPS,生产部署必须前置 nginx 终结 TLS
default = true会让所有pip install默认走私有源,公网包找不到时报错;正确做法是配置多个 index 并设 fallback
方案 B:Nexus Repository(企业级,重型)
Nexus 是 Sonatype 出的通用制品库,支持 PyPI / npm / Maven / Docker 一站式管理。适合已经有 Nexus / 团队规模 50+ 的公司。
# 1. 部署 Nexus(略,见官方文档)
# 2. 创建 pypi-hosted 仓库(上传公司内部包)
# 3. 创建 pypi-proxy 仓库(代理公网 PyPI)
# 4. 创建 pypi-group 仓库(合并 hosted + proxy)
# URL: https://nexus.mycompany.com/repository/pypi-group/
uv 配置:
[[tool.uv.index]]
name = "mycompany-group"
url = "https://nexus.mycompany.com/repository/pypi-group/simple"
default = true
方案 C:bandersnatch(只做镜像,适合”快”需求)
如果只是加速公网 PyPI 拉取、不需要托管内部包,bandersnatch 是 PyPA 官方维护的镜像工具:
pip install bandersnatch
# 生成配置
bandersnatch mirror --config-file=/etc/bandersnatch.conf
# /etc/bandersnatch.conf
[mirror]
directory = /srv/pypi
master = https://pypi.org
timeout = 30
workers = 10
# 首次同步(全量,几小时)
bandersnatch mirror --config-file=/etc/bandersnatch.conf
# 之后用 cron 每 10 分钟跑一次(增量)
*/10 * * * * bandersnatch mirror --config-file=/etc/bandersnatch.conf
踩坑点:
- 全量同步需要 2TB+ 磁盘,增量同步通常 < 100MB
- 不支持私有包托管,只是 PyPI 镜像
国内镜像加速清单(2026-07 实测可达)
| 镜像 | URL | 维护方 | 备注 |
|---|---|---|---|
| 清华 TUNA | https://pypi.tuna.tsinghua.edu.cn/simple |
清华大学 | 首选,稳定 |
| 阿里云 | https://mirrors.aliyun.com/pypi/simple/ |
阿里云 | 备份 |
| 腾讯云 | https://mirrors.tencent.com/pypi/simple |
腾讯云 | 备份 |
| 中科大 | https://pypi.mirrors.ustc.edu.cn/simple/ |
中科大 | 备用 |
uv 临时指定:
UV_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple uv sync
5. pyproject.toml 完整解读
5.1 [project] 段:PEP 621 项目元数据
这是 PEP 621 规定的强制核心字段,任何 pyproject.toml 都要有:
[project]
name = "myproject" # 包名(必须小写、可用 - 和 _)
version = "0.1.0" # 版本号,推荐用 hatch-vcs 从 git tag 读(详见 §6.2)
description = "An example package" # 一句话描述
readme = "README.md" # README 路径
requires-python = ">=3.11" # 支持的 Python 版本范围
license = { text = "MIT" } # SPDX 标识符或 text 字段
authors = [
{ name = "Alice", email = "alice@example.com" },
]
keywords = ["example", "demo"]
classifiers = [ # PyPI 分类标签
"Development Status :: 4 - Beta",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]
# 运行时依赖(必须)
dependencies = [
"requests>=2.28",
"pydantic>=2.6,<3",
"httpx>=0.27",
]
5.2 [project.optional-dependencies] 段:可选依赖组
PEP 621 把可选依赖分组到 [project.optional-dependencies],典型用法:
[project.optional-dependencies]
# dev 组:开发时用,生产环境不装
dev = [
"pytest>=8.0",
"pytest-cov>=5.0",
"ruff>=0.6",
"mypy>=1.10",
]
# test 组:CI 用
test = [
"pytest>=8.0",
"pytest-asyncio>=0.23",
]
# docs 组:文档构建
docs = [
"mkdocs>=1.5",
"mkdocs-material>=9.5",
"pymdown-extensions>=10.0",
]
# mysql 组:可选的数据库驱动
mysql = ["mysqlclient>=2.2"]
postgres = ["psycopg2-binary>=2.9"]
装法:
# pip
pip install ".[dev,test]"
# poetry
poetry install --with dev,test
# uv(uv 不走 optional-dependencies,走 PEP 735 dependency-groups,见 §5.3)
5.3 [dependency-groups]:PEP 735 现代化替代(uv 推荐)
PEP 735(2024 通过)是 optional-dependencies 的现代化替代,uv / pip-tools 2024+ 已支持:
# pyproject.toml
[dependency-groups]
dev = [
"pytest>=8.0",
"ruff>=0.6",
"mypy>=1.10",
]
test = ["pytest>=8.0", "pytest-asyncio>=0.23"]
docs = ["mkdocs>=1.5"]
装法:
uv sync --group dev # uv
pip-compile --group dev # pip-tools
核心优势:不污染 [project.optional-dependencies],库的发布元数据更干净。应用项目推荐用 PEP 735,库项目用 optional-dependencies。
5.4 [project.scripts] 段:命令行入口
[project.scripts]
mycli = "myproject.cli:main" # 安装后生成 mycli 命令
myother = "myproject.other:run" # 可声明多个
# Windows 下需要 [project.gui-scripts](很少用)
对应源码:
# src/myproject/cli.py
def main():
"""mycli 的入口函数"""
import argparse
parser = argparse.ArgumentParser()
parser.parse_args()
print("hello from mycli")
装包后:
pip install -e .
mycli # 直接调用
5.5 [project.urls] / [project.license] 段
[project.urls]
Homepage = "https://example.com"
Repository = "https://github.com/myorg/myproject"
Documentation = "https://docs.example.com"
Changelog = "https://github.com/myorg/myproject/blob/main/CHANGELOG.md"
Issues = "https://github.com/myorg/myproject/issues"
[project.license]
file = "LICENSE" # 推荐用 file 而非 text,会被 PyPI 显示为标准 license 标识
5.6 [build-system] 段:build-backend 选择
PEP 517 规定:pyproject.toml 必须声明 build-system,告诉工具用什么后端构建:
[build-system]
requires = ["hatchling"] # 构建时的依赖
build-backend = "hatchling.build" # 构建后端的 Python 路径
三种主流后端对比:
| 后端 | 推荐场景 | 优点 | 缺点 |
|---|---|---|---|
| hatchling | 库作者首选 | PyPA 官方、配置极简 | 复杂需求要写 plugin |
| setuptools | 兼容老项目 | 历史悠久、生态最广 | 配置啰嗦、setup.py 残留多 |
| pdm-backend | 用 pdm 的项目 | 与 pdm 深度集成 | 其他工具不一定兼容 |
uv 项目推荐:
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["src/myproject"] # src layout 推荐
5.7 [tool.uv] / [tool.poetry] / [tool.ruff] 工具专属配置
各工具在 [tool.<name>] 段下放专属配置,互不污染:
# uv 专属
[tool.uv]
dev-dependencies = ["pytest>=8.0", "ruff>=0.6"]
managed = true # uv 自动管理 venv
# poetry 专属
[tool.poetry]
name = "myproject"
version = "0.1.0"
[tool.poetry.dependencies]
python = "^3.11"
# ruff 专属(代码风格)
[tool.ruff]
line-length = 100
target-version = "py311"
[tool.ruff.lint]
select = ["E", "F", "I", "N", "W", "UP"]
# mypy 专属(类型检查)
[tool.mypy]
python_version = "3.11"
strict = true
关键规则:[project] / [build-system] 段是 PEP 标准,跨工具通用;[tool.*] 段是各工具私有,工具切换时要重新写。
6. 高级话题
6.1 包发布到 PyPI 的完整流程(trusted publishing / OIDC)
传统方式(已不推荐):注册 PyPI 账号 + 在本地配 token + poetry publish。
现代方式(2024 起 PyPI 推荐):Trusted Publishing(OIDC),无需 token,CI 自动认证。
流程 1:在 PyPI 上配置 trusted publisher
访问 https://pypi.org/manage/account/publishing/,填:
- Owner:你的用户名 / 组织名
- Repository name:
myproject - Workflow filename:
.github/workflows/release.yml - Environment name:
pypi
流程 2:GitHub Actions 工作流
# .github/workflows/release.yml
name: Publish to PyPI
on:
push:
tags:
- "v*" # 推送 v0.1.0 这样的 tag 触发
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v3
- name: Set up Python
run: uv python install 3.12
- name: Build sdist + wheel
run: uv build
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
# 无需 token!通过 OIDC 自动认证
流程 3:推送 tag 触发发布
git tag v0.1.0
git push origin v0.1.0
# CI 自动构建并发布到 PyPI,无需人工干预
优势:
- 无需在 CI 配置 token(OIDC 自动换短时凭证)
- 不存在 token 泄露风险
- PyPI 自动验证 GitHub workflow 的真实性
调研依据:Trusted Publishing 自 2023 年 PyPI 推广,2024 起成为 PyPI 官方推荐方式。详细见 docs.pypi.org/trusted-publishers(沙箱可达,2026-07-05 验证)。
6.2 动态版本:hatch-vcs 从 git tag 读
痛点:手动维护 version = "0.1.0" 容易出错,每次发布要手动改。
方案:hatch-vcs 从 git tag 自动读版本号。
步骤 1:装 hatch-vcs 作为构建依赖
# pyproject.toml
[build-system]
requires = ["hatchling", "hatch-vcs"]
build-backend = "hatchling.build"
[tool.hatch.version]
source = "vcs" # 从 git 读
步骤 2:加 tag → 版本自动对齐
git tag v0.1.0
git push origin v0.1.0
# build 时,版本号自动 = 0.1.0
uv build
# 产物 myproject-0.1.0-py3-none-any.whl
# 产物 myproject-0.1.0.tar.gz
踩坑点:
- git tag 必须严格
v<semver>格式(否则 hatch-vcs 不识别) - 第一次构建必须 git commit + git tag 都齐全
6.3 C 扩展打包:setuptools vs scikit-build vs maturin
场景:你的库有 C / C++ / Rust 代码,需要编译成 wheel。
| 后端 | 语言 | 推荐场景 |
|---|---|---|
| setuptools + Extension | C / C++ | 简单 C 扩展、传统项目 |
| scikit-build + CMake | C / C++ | 科学计算库(numpy / scipy 风格) |
| maturin | Rust | Rust 实现的 Python 绑定(pydantic-core / orjson) |
方案 A:setuptools + C(简单)
[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"
[tool.setuptools]
ext-modules = [
{ name = "myproject.fastmath", sources = ["src/fastmath.c"] },
]
方案 B:scikit-build + CMake(科学计算)
[build-system]
requires = ["scikit-build-core>=0.9"]
build-backend = "scikit_build_core.build"
[tool.scikit-build]
wheel.packages = ["src/myproject"]
CMakeLists.txt:
cmake_minimum_required(VERSION 3.15)
project(myproject LANGUAGES C)
# 找 Python 头文件
find_package(Python COMPONENTS Interpreter Development.Module)
add_library(fastmath MODULE src/fastmath.c)
target_link_libraries(fastmath PRIVATE Python::Module)
方案 C:maturin + Rust(现代高性能)
[build-system]
requires = ["maturin>=1.5"]
build-backend = "maturin"
[tool.maturin]
module-name = "myproject._native"
features = ["pyo3/extension-module"]
Cargo.toml:
[lib]
name = "_native"
crate-type = ["cdylib"]
[dependencies]
pyo3 = { version = "0.21", features = ["extension-module"] }
优势对比:
| 维度 | setuptools | scikit-build | maturin |
|---|---|---|---|
| 学习曲线 | 平缓 | 陡(CMake) | 中(Rust) |
| 跨平台 wheel | 手动 / cibuildwheel | cibuildwheel | 自动(maturin action) |
| 性能上限 | 看 C 代码 | 看 C++ 代码 | 最高(Rust) |
踩坑点:
- C 扩展的 wheel 必须 cibuildwheel 才能跨平台(本地 macOS 没法 build Linux wheel)
- maturin 项目必须有 Rust 工具链(CI 用
manylinux镜像)
6.4 Docker 多阶段构建(Builder + Runtime)
核心原则:构建环境和运行环境分离。生产镜像不应该装编译工具。
完整 Dockerfile(uv + Python 3.12)
# ===== Stage 1: Builder =====
FROM python:3.12-slim AS builder
# 装 uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/
# 设置工作目录
WORKDIR /app
# 先复制 lock + pyproject,利用 Docker layer 缓存
COPY pyproject.toml uv.lock ./
# 装依赖到独立目录
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-dev --no-install-project
# 再复制源码,装项目本身
COPY src ./src
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-dev
# ===== Stage 2: Runtime =====
FROM python:3.12-slim AS runtime
# 复制 venv(包含所有依赖 + 项目)
COPY --from=builder /app/.venv /app/.venv
# 设置 PATH 指向 venv
ENV PATH="/app/.venv/bin:$PATH"
# 非 root 运行
RUN useradd -m -u 1001 appuser
USER appuser
WORKDIR /app
# 启动命令
CMD ["python", "-m", "myproject.main"]
镜像大小对比
| 方案 | 镜像大小 | 备注 |
|---|---|---|
单阶段 python:3.12 + pip install |
~800MB | 含 gcc / pip cache |
| 多阶段 Builder + Runtime(本节) | ~180MB | ✅ 推荐 |
| Alpine 镜像 | ~80MB | 不推荐(musl 兼容性问题,numpy 慢) |
关键优化:
--mount=type=cache,target=/root/.cache/uv—— uv 缓存跨构建复用--no-dev—— 不装测试/lint 工具--frozen—— 严格按 lock 装,不重新解析
docker-compose 集成
# docker-compose.yml
services:
api:
build: .
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql://user:pass@db:5432/mydb
depends_on:
- db
db:
image: postgres:16-alpine
environment:
- POSTGRES_PASSWORD=pass
7. 评估方式 + 参考资料 + 关联模块
7.1 评估方式(达到这个深度的标志)
3-5 年档:能选型
- 拿到一个新项目需求,5 分钟内能判断用 pip / pip-tools / poetry / uv / conda 哪个
- 能解释为什么 uv 比 poetry 快(Rust 实现 + 共享缓存 + 静态解析)
- 能写完整的
pyproject.toml([project]/[build-system]/ 依赖分组) - 能在 CI 里配 lock 同步(
uv sync --frozen/pip-sync) - 能用国内镜像加速
pip install(配置pip.conf或UV_INDEX_URL)
5-10 年档:能为大型项目设计可重现流程
- 能搭私有 PyPI 源(devpi / Nexus)
- 能配 uv workspace 处理 monorepo(10+ 子包)
- 能用 trusted publishing(OIDC)发包,不需要 token
- 能用 hatch-vcs 从 git tag 自动读版本号
- 能用 maturin 把 Rust 代码打包成 wheel
- 能写多阶段 Docker 构建,生产镜像 < 200MB
- 能诊断”装出版本不对”的问题(看 lock + 看镜像源)
7.2 关联模块
- [1.3 Python 高级特性] — 类型注解与
pyproject.toml跨工具配置交叉 - [1.3.2 Python 类型系统] —
[tool.mypy]/[tool.pyright]是工具专属段典型 - [1.3.3 Python 性能调优] — C 扩展打包(scikit-build / maturin)是性能优化落地的最后一公里
- [2.1 LLM 工程化] — AI 项目往往用 uv + 国内镜像加速 PyTorch / transformers
- [3.2 DevOps 基础] — Docker 多阶段构建 + trusted publishing 是 CI/CD 落地关键
7.3 参考资料(全部可访问)
官方文档
- PEP 517 — Build system requirements
- PEP 518 — Specifying build dependencies
- PEP 621 — Storing project metadata in pyproject.toml
- PEP 735 — Dependency Groups
- Python Packaging User Guide
- PyPI Trusted Publishers
工具文档
包 / 工具仓库
7.4 未独立验证的事实(透明声明)
| 事实 | 状态 | 说明 |
|---|---|---|
| uv 比 poetry 快 10-100 倍 | ⚠️ 量级对,具体倍数因场景而异 | 见 §3.6 表格脚注 |
| pip 装包平均 45s | ❌ | 没有直接测量样本,本节数字是经验估算 |
| 依赖问题占 Python 项目失败的 30% | ❌ | 基于团队案例的经验估算,非大规模样本统计 |
| Rye 2024-08 被作者弃坑 | ✅ 已验证 | 见 §2.2 |
| Trusted Publishing 是 PyPI 官方推荐 | ✅ 已验证 | 见 §6.1 |
| uv workspace 自 0.4 版本起可用 | ✅ 已验证 | 见 §4.2 |
7.5 沙箱内不可达资源
| 资源 | URL | 替代方案 |
|---|---|---|
| GitHub README raw | https://raw.githubusercontent.com/... |
改用 PyPI 页面验证版本与说明 |
| GitHub issue / commit | https://github.com/.../issues/N |
改用 PyPI release notes / changelog |
| 部分英文博客 | 中等可靠性 | 改用官方文档 + arxiv 论文 |
本节要点(8 条压缩结论)
-
Python 包管理 4 个核心问题:依赖锁定 / 虚拟环境隔离 / 私有源 / 镜像加速。一个合格的工程化项目必须 4 个都有解。
-
工具演进已收敛:
distutils → setuptools → pip → pip-tools / poetry / pdm / hatch / uv。uv 是 2026 年新项目首选,poetry 不会被立刻淘汰但增量已降;Rye / pipenv / distutils 已弃。 -
uv 的核心优势是速度(10-100 倍),代价是生态还在完善。大型企业建议观望 6-12 个月再迁移,新项目建议直接用。
-
uv workspace(自 0.4 版本起可用)是 monorepo 多项目共享依赖的最优解:lock 统一 + 装包极快 + 成员之间无循环依赖(设计而非 bug)。
-
pyproject.toml是 2026 年的事实标准:[project]/[build-system]段是 PEP 标准跨工具通用;[tool.*]段是工具私有。PEP 621 + PEP 735 + PEP 517/518 三件套必须熟。 -
私有源三层架构:本地配置 index → 公司 Nexus / devpi 代理 → 公网 PyPI + 国内镜像。devpi 适合中小团队,Nexus 适合 50+ 团队。
-
Trusted Publishing(OIDC)是 PyPI 2024+ 官方推荐,CI 自动认证无需 token;hatch-vcs 从 git tag 自动读版本号;maturin 是 Rust 扩展打包的事实标准。
-
Docker 多阶段构建(Builder + Runtime)能让生产镜像从 800MB 降到 180MB,配合
--mount=type=cache让 uv 缓存跨构建复用。Alpine 不推荐(musl + numpy 兼容性差)。
下一节预告
下一节是 1.3.5 Python 测试体系 · 从 pytest 到契约测试(按难度路线图的下一步)。
预告要点:pytest 的 fixture / parametrize / plugin 体系 → 测试金字塔(单元 / 集成 / E2E)→ property-based testing(Hypothesis)→ mutation testing(mutmut)→ 契约测试(Pact)→ 性能回归测试(pytest-benchmark)。
前置:1.3 Python 高级特性(类型注解) + 1.3.4 本篇包管理(知道怎么装 pytest + 怎么配 lock 才能保证测试结果可重现)。