专栏 知识宝典 子专栏 编程语言精进 23 篇

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 参考资料(全部可访问)

官方文档

工具文档

包 / 工具仓库

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 条压缩结论)

  1. Python 包管理 4 个核心问题:依赖锁定 / 虚拟环境隔离 / 私有源 / 镜像加速。一个合格的工程化项目必须 4 个都有解。

  2. 工具演进已收敛:distutils → setuptools → pip → pip-tools / poetry / pdm / hatch / uv。uv 是 2026 年新项目首选,poetry 不会被立刻淘汰但增量已降;Rye / pipenv / distutils 已弃。

  3. uv 的核心优势是速度(10-100 倍),代价是生态还在完善。大型企业建议观望 6-12 个月再迁移,新项目建议直接用。

  4. uv workspace(自 0.4 版本起可用)是 monorepo 多项目共享依赖的最优解:lock 统一 + 装包极快 + 成员之间无循环依赖(设计而非 bug)。

  5. pyproject.toml 是 2026 年的事实标准:[project] / [build-system] 段是 PEP 标准跨工具通用;[tool.*] 段是工具私有。PEP 621 + PEP 735 + PEP 517/518 三件套必须熟。

  6. 私有源三层架构:本地配置 index → 公司 Nexus / devpi 代理 → 公网 PyPI + 国内镜像。devpi 适合中小团队,Nexus 适合 50+ 团队。

  7. Trusted Publishing(OIDC)是 PyPI 2024+ 官方推荐,CI 自动认证无需 token;hatch-vcs 从 git tag 自动读版本号;maturin 是 Rust 扩展打包的事实标准。

  8. 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 才能保证测试结果可重现)。

说明 · 本站内容均为学习笔记与经验总结,所有菜谱与技法请结合实际食材、季节与个人口味灵活调整。涉及生食、营养与健康的内容仅供参考,特殊体质或疾病请咨询专业营养师/医生。