uv实战指南-从Python版本虚拟环境到pyproject依赖管理

目录

  • [uv 实战指南:从 Python 版本、虚拟环境到 pyproject.toml 依赖管理](#uv 实战指南:从 Python 版本、虚拟环境到 pyproject.toml 依赖管理)
    • [1. Python 项目为什么总出现"环境问题"?](#1. Python 项目为什么总出现“环境问题”?)
    • [2. uv 是什么?它与 Python 是什么关系?](#2. uv 是什么?它与 Python 是什么关系?)
    • [3. uv 与 pip、venv、pipx、Poetry 有什么区别?](#3. uv 与 pip、venv、pipx、Poetry 有什么区别?)
    • [4. uv 怎么安装?Windows、macOS、Linux 分别怎么做?](#4. uv 怎么安装?Windows、macOS、Linux 分别怎么做?)
    • [5. uv 怎么创建 Python 项目?先看实际模板](#5. uv 怎么创建 Python 项目?先看实际模板)
    • [6. pyproject.toml 是什么?为什么用它声明依赖?](#6. pyproject.toml 是什么?为什么用它声明依赖?)
    • [7. uv 怎么管理 Python 版本?](#7. uv 怎么管理 Python 版本?)
    • [8. uv 怎么创建虚拟环境?要手动 activate 吗?](#8. uv 怎么创建虚拟环境?要手动 activate 吗?)
    • [9. uv add 怎么安装项目依赖?与 pip install 差在哪?](#9. uv add 怎么安装项目依赖?与 pip install 差在哪?)
    • [10. uv.lock 是什么?它为什么不等于 requirements.txt?](#10. uv.lock 是什么?它为什么不等于 requirements.txt?)
    • [11. uv sync 怎么用?从 GitHub 克隆后做什么?](#11. uv sync 怎么用?从 GitHub 克隆后做什么?)
    • [12. uv run 怎么用?它会不会绕过虚拟环境?](#12. uv run 怎么用?它会不会绕过虚拟环境?)
    • [13. uv remove 怎么删除依赖?](#13. uv remove 怎么删除依赖?)
    • [14. 开发依赖怎么管理?](#14. 开发依赖怎么管理?)
    • [15. requirements.txt 项目如何迁移到 uv?](#15. requirements.txt 项目如何迁移到 uv?)
    • [16. requirements.txt 还需要吗?](#16. requirements.txt 还需要吗?)
    • [17. uv tool 怎么安装命令行工具?](#17. uv tool 怎么安装命令行工具?)
    • [18. uvx 是什么?与正式安装有何区别?](#18. uvx 是什么?与正式安装有何区别?)
    • [19. 克隆 uv 项目后怎么运行?哪些文件要上传 GitHub?](#19. 克隆 uv 项目后怎么运行?哪些文件要上传 GitHub?)
    • [20. uv + Git 怎么协作?](#20. uv + Git 怎么协作?)
    • [21. Docker 中如何使用 uv?](#21. Docker 中如何使用 uv?)
    • [22. CI 中如何使用 uv?](#22. CI 中如何使用 uv?)
    • [23. uv 缓存放在哪里?什么时候需要清理?](#23. uv 缓存放在哪里?什么时候需要清理?)
    • [24. 常见错误怎么排查?](#24. 常见错误怎么排查?)
    • [25. uv 与 Conda 是什么关系?](#25. uv 与 Conda 是什么关系?)
    • [26. uv 与 Poetry 如何理解?](#26. uv 与 Poetry 如何理解?)
    • [27. 从零完成一个可复现的 FastAPI 项目](#27. 从零完成一个可复现的 FastAPI 项目)
      • [27.1 初始化、添加运行与开发依赖](#27.1 初始化、添加运行与开发依赖)
      • [27.2 完整配置与 API 源码](#27.2 完整配置与 API 源码)
      • [27.3 测试代码、启动与实际响应](#27.3 测试代码、启动与实际响应)
      • [27.4 Git 提交、新机器恢复与清理](#27.4 Git 提交、新机器恢复与清理)
    • [28. uv 适合什么项目?](#28. uv 适合什么项目?)
    • [29. uv 不应该被神化:下一步怎么维护?](#29. uv 不应该被神化:下一步怎么维护?)
    • 官方资料与验证边界

uv 实战指南:从 Python 版本、虚拟环境到 pyproject.toml 依赖管理

专栏 :工具|状态 :待发布稿|核对日期 :2026-10-02。命令与示例项目在 uv 0.12.19 、CPython 3.12.14 、Linux 环境验证;Windows PowerShell、Docker 与 GitHub Actions 操作依据当日官方文档核对,未冒充本机实测。uv 持续更新,界面、默认模板和最新依赖版本应以安装时的 uv --version 与官方文档为准。

阅读路线 :1--3 章先分清职责;4--14 章建立日常工作流;15--20 章迁移与协作;21--26 章集成和排障;27 章做完整项目。配套项目源码、锁文件和验证记录可直接下载运行。

先记住一条主线 :pyproject.toml 声明项目需要什么;uv.lock 固定解析结果;.venv 是本机安装结果;uv sync 让本机环境跟随声明和锁文件;uv run 在这个环境中运行程序。它们解决的是不同层面的问题。

图 1:左侧是手工维护 venv、pip、requirements.txt 的常见流程,右侧是 uv 项目流程。箭头表示管理步骤,不表示 pip install 会自动写出 requirements.txt;两个流程都可以规范管理项目。

1. Python 项目为什么总出现"环境问题"?

假设项目 A 要 Python 3.10、NumPy 1.x,项目 B 要 Python 3.12、NumPy 2.x。把两套依赖都装进同一个系统 Python,后装的包可能改变前一个项目的运行条件。即使两个项目都使用 requests 2.x,其依赖树和可用 Python 范围也会随着具体版本变化。

这类问题有四个层次:解释器版本 决定语言和扩展模块兼容范围;虚拟环境 把项目已安装的包隔开;项目声明 告诉团队哪些包是直接需求;锁文件记录解析后选中的版本和平台条件。只分享源码,缺少其余信息,新电脑便可能得到另一套环境。

python -m venv .venv 已经可以隔离包,requirements.txt 也能记录依赖;问题常出在团队忘记更新文件、把 pip freeze 的所有间接依赖当成直接需求、或漏掉 Python 版本和系统依赖。uv 把若干步骤连成项目工作流,但这些问题仍要由开发者识别和维护。12

2. uv 是什么?它与 Python 是什么关系?

uv 是 Astral 提供的 Python 包与项目管理工具,支持发现或安装 Python、创建虚拟环境、声明与解析依赖、生成锁文件、同步环境、运行项目命令,以及隔离运行命令行工具。它本身不是 Python 解释器:执行 uv run python ... 时,仍由选中的 Python 解释器运行代码。13

一个 uv 项目的日常链路是:

图 2:uv init 首先创建 pyproject.toml;uv add 修改依赖声明并解析出 uv.lock,通常还会同步 .venv。图中单独画出 uv sync,是为了说明别人克隆项目后的显式恢复步骤。

它也不是 IDE、部署平台或操作系统包管理器。比如项目需要系统级数据库客户端库或 CUDA 驱动,锁住 Python 包并不能自动提供这些组件。

3. uv 与 pip、venv、pipx、Poetry 有什么区别?

工具 常见职责 在项目中的位置
pip 把包安装到指定 Python 环境 与 venv、requirements 文件搭配;本身不替你维护项目声明
标准库 venv 建立隔离的 Python 环境 隔离安装结果,不解析项目依赖
pipx 将 Python CLI 工具隔离安装并暴露命令 适合用户级命令行工具
Poetry 项目元数据、依赖解析、锁定和运行 已采用的团队应考虑现有配置及迁移成本
uv 覆盖上述多个常见环节,并提供 uv pip 兼容式接口 可用项目模式,也可只替换某一环节

区别在职责,不在胜负 。uv add requests 会记录项目直接依赖并更新锁文件;pip install requests 安装到当前环境,项目文件是否同步更新由你另行处理。已经稳定使用 pip + venv、pip-tools 或 Poetry 的项目,并不因安装 uv 就必须迁移。uv pip 与 uv 项目模式也不同:前者能安装 requirements,后者维护 pyproject.toml 和 uv.lock。14

4. uv 怎么安装?Windows、macOS、Linux 分别怎么做?

官方提供独立安装脚本。5 Windows PowerShell 中执行:

powershell 复制代码
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
uv --version

第一行下载并运行官方安装脚本,临时对该进程使用 ByPass;第二行确认当前终端能找到 uv。先在官方安装页核对脚本地址,受企业安全策略限制时可下载发行版二进制文件。这里的执行策略只用于这一条 PowerShell 命令,不是让读者永久放宽整台机器的策略。

macOS / Linux 终端中执行:

bash 复制代码
curl -LsSf https://astral.sh/uv/install.sh | sh
uv --version

curl 下载官方脚本,sh 执行,uv --version 验证命令可用。如果刚安装完显示"找不到 uv",关闭并重新打开终端;用 Windows 的 Get-Command uv,或 macOS/Linux 的 command -v uv 检查 PATH。脚本的安装位置以实际输出和官方安装说明为准;不要把某位作者电脑上的固定路径当作所有系统的路径。本文以下命令以 uv 0.12 系列核对,不要求读者版本号与作者完全一致。

5. uv 怎么创建 Python 项目?先看实际模板

在尚无同名目录的工作区运行:

bash 复制代码
uv init demo-project --python 3.12
cd demo-project

uv init 建立项目,--python 3.12 指定初始化时采用的 Python 版本要求,cd 进入目录。uv 0.12.19 本机默认应用模板实际生成的关键结构为:

text 复制代码
demo-project/
├── .python-version
├── README.md
├── pyproject.toml
└── src/
    └── demo_project/
        └── __init__.py

它还在 pyproject.toml 中写入 [project.scripts] 与 uv_build 的 [build-system],源码可作为包安装。默认作者字段和 requires-python 会随本机配置及指定解释器有所不同。旧版 uv 曾默认生成顶层 main.py;不要照着旧教程断言当前默认目录一定长那样。官方注明应用模板在 0.12 前后的变化。6

初始化并不等于依赖已下载:.venv 和 uv.lock 会在首次需要同步时创建。第 27 章为了聚焦 Web 项目,显式使用 uv init --no-package,因此结构与这个默认模板不同。

6. pyproject.toml 是什么?为什么用它声明依赖?

pyproject.toml 是 Python 打包生态使用的标准配置文件;[project] 存放项目元数据与依赖,[build-system] 可声明如何构建,[tool.<name>] 可放具体工具配置。它不专属于 uv。78

toml 复制代码
[project]
name = "demo-project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["requests>=2.32"]

name 是项目/发行包名称;version 是项目自己的版本,不是 Python 版本;requires-python 描述支持的解释器范围 ,>=3.12 不等于"强制只在 3.12 测试";dependencies 放直接运行依赖,版本约束用 Python 包依赖规范表达。构建/发布项目还需根据项目模板保留合适的 readme、[build-system] 等;这里是演示字段职责的简化片段,不是声称每个项目只需四行。

团队应把"代码里直接 import 哪些第三方包"和"这些包又依赖哪些包"分开。前者是自己的声明;后者由解析器根据约束生成锁定结果。手工改 pyproject.toml 可以,但改完要让锁文件重新解析;uv add 能把这些步骤串起来。

7. uv 怎么管理 Python 版本?

bash 复制代码
uv python list --only-installed
uv python install 3.12
uv python pin 3.12
uv run python --version

第一行列出已安装且可发现的解释器,便于判断本机究竟有什么;第二行在需要 3.12 且本机没有合适解释器时,由 uv 安装其管理的 3.12 最新可用补丁版本;第三行在项目目录 写 .python-version,让本项目优先请求 3.12;第四行验证项目实际运行的解释器。uv 在需要时也可自动下载 Python;受内网约束时先核对下载策略。3

"系统 Python"是 uv 没有管理的解释器,包括通过操作系统或其他工具安装的 Python;"uv 管理的 Python"是 uv 下载的发行版;"项目 Python"是此项目最终选择并用于 .venv 的那个解释器。三者名称说的是来源与用途 ,不代表系统上必须装三份。.python-version 选择倾向与 requires-python 的声明职责也不同:前者通常指导本地选择,后者是项目兼容范围;两者应相容。3

8. uv 怎么创建虚拟环境?要手动 activate 吗?

bash 复制代码
uv venv --python 3.12

这会在当前目录创建 .venv,目录下有自己的解释器入口和包安装位置。通常不要提交 .venv:它体积大、路径和平台有关,可以根据项目文件再造。uv init 后不必须先运行这条命令 ;uv sync 或 uv run 在项目中按需创建并维护 .venv。29

需要在当前 Shell 中直接运行 python、pytest 时可以激活它:

powershell 复制代码
# Windows PowerShell
.\.venv\Scripts\Activate.ps1
bash 复制代码
# macOS / Linux / WSL
source .venv/bin/activate

Windows CMD 则用 .venv\Scripts\activate.bat。激活本质是调整当前 Shell 的命令查找路径;不是环境是否存在的开关。用 uv run python -V、uv run python app.py 时,uv 直接选择项目环境,通常无需手动激活;如果 IDE 没识别,手动选择 .venv 中的解释器即可。

9. uv add 怎么安装项目依赖?与 pip install 差在哪?

进入 demo-project 后执行:

bash 复制代码
uv add requests
uv add fastapi

每一行会把指定包加进 pyproject.toml 的直接依赖,解析符合整个项目约束的依赖树,更新 uv.lock 并同步项目环境。运行后看两个文件:声明中可见 requests>=某版本、fastapi>=某版本 一类约束;锁文件还包含它们需要的间接依赖及具体选择。实际版本取决于执行时索引可用版本,不把本文示意写成永久固定值。4

uv add 不等于只做一次 pip install 。后者主要改变目标环境;前者改变项目声明和锁文件,让其他人也有可重建的依据。不要在 uv 项目里只向 .venv 手工安装一个包就宣布"项目已经增加该依赖";下次同步可能把未声明的包移走。

10. uv.lock 是什么?它为什么不等于 requirements.txt?

如果 pyproject.toml 写 fastapi>=0.142,它表达"允许的范围",没有说明哪一个具体版本以及 FastAPI 所依赖的 Starlette、Pydantic 等最终如何选择。uv.lock 记录解析结果、包来源、依赖关系及用于不同环境的分支信息;uv 的项目锁文件旨在兼顾受支持的平台和 Python 版本,不必为每个平台机械维护一份文件。910

文件 主要回答 是否提交 Git
pyproject.toml 我们直接需要哪些包,项目兼容哪些 Python? 是
uv.lock 这些约束当前解析到哪些包与版本? 应用项目通常是
.python-version 本地默认选哪个 Python 系列? 团队决定统一 3.12 时通常是
.venv/ 当前机器实际安装了什么? 通常否

requirements.txt 是安装需求列表的常见格式:可以只有宽松约束,也可以有精确版本和哈希,复杂程度取决于生成方式。uv.lock 则是 uv 项目解析器的专用格式,能表示依赖组、不同平台的条件等。不能说所有 requirements 都"不能锁版本",也不能把两个文件简单改名互换。1011

锁文件也不是跨系统镜像:相同版本可能选不同 wheel;项目外的操作系统库、CPU 架构、CUDA 驱动不会由它全部锁住。锁定与测试要一起看。需要主动升级时使用 uv lock --upgrade-package 包名,而非期待 uv sync 每次自动升到最新版。9

11. uv sync 怎么用?从 GitHub 克隆后做什么?

bash 复制代码
git clone <真实仓库地址>
cd <项目目录>
uv sync

占位符需要替换为你能访问的实际仓库;不是可直接请求的地址。uv sync 查验声明和锁文件、选择兼容的 Python、创建或调整 .venv、把锁定依赖装进项目环境。开发依赖 dev 默认包含在同步中;生产环境可按项目要求使用 uv sync --no-dev。9

协作或 CI 更适合明确要求锁文件是新的:

bash 复制代码
uv sync --locked
uv lock --check

前者同步且在锁文件与项目声明不一致时报错而不改锁文件 ;后者只检查锁是否需更新。这与 --frozen 有区别:--frozen 使用锁文件而不验证其新旧,容易掩盖漏提交的声明修改。普通本地开发用 uv sync 可自动更新,CI/发布检查用 --locked 更能暴露协作遗漏。9

12. uv run 怎么用?它会不会绕过虚拟环境?

bash 复制代码
uv run python -V
uv run python -m pytest -q
uv run uvicorn app.main:app --reload

第一行显示项目选择的 Python 版本;第二行在项目环境运行 pytest;第三行启动本篇示例 API,并在本地开发时监视源码变化。真正运行完整案例前先看第 27 章,确保项目和测试依赖已添加。uv run 默认先检查锁和环境,再执行给定命令;虚拟环境仍存在,只是不用每个终端都 activate。12

如果直接运行系统的 python,也许会导入另一套包。遇到"明明装了却 ModuleNotFoundError",对比 uv run python -c "import sys; print(sys.executable)" 与当前终端的 python 位置,先确认到底用了哪个解释器。

13. uv remove 怎么删除依赖?

bash 复制代码
uv remove requests

这会从项目直接依赖中移除 requests,重新解析锁文件并同步环境。若其他包仍需要它,某个同名包仍可能作为间接依赖 存在;不应只凭环境里还能 import requests 判断删除失败。也不要只手工从虚拟环境卸载它,却保留在 pyproject.toml:下次同步就会装回来。4

删除前搜索源码和测试引用,删除后运行测试并提交 pyproject.toml 与 uv.lock 的变化。如果仅想暂时对比环境,不要把试验误写进团队的正式依赖文件。

14. 开发依赖怎么管理?

测试和 Lint 工具通常不需要随应用部署。uv 当前使用标准化的 dependency groups 表达,例如:

bash 复制代码
uv add --dev pytest httpx

这会把两项放到 pyproject.toml 的 dev 组,而不是 [project].dependencies;本篇案例的 HTTP 测试用到 pytest 与 httpx。示意文件片段:

toml 复制代码
[dependency-groups]
dev = ["httpx>=0.28.1", "pytest>=9.1.1"]

版本是配套项目在 2026-10-02 解析的结果,不是推荐所有项目固定为此版本。开发环境 uv sync 默认包含 dev 组;部署时用 uv sync --no-dev 跳过。某工具如果是应用运行本身所必需,例如此例的 uvicorn 服务器,必须放在运行依赖,不能只放 dev。48

15. requirements.txt 项目如何迁移到 uv?

假设旧仓库是:

text 复制代码
project/
├── main.py
└── requirements.txt

其中 requirements.txt 由团队手写、只包含直接依赖,例如:

text 复制代码
requests==2.32.5
Flask==3.1.2

先确认 requirements 的来源 。若确实是这类直接依赖列表,可在现有仓库根目录运行 uv init --no-package --python 3.12(已有 pyproject 时不要重复初始化),然后:

bash 复制代码
uv add -r requirements.txt
uv run python main.py

-r 读取文件中的包约束并加入项目声明;下一行在新环境执行脚本。本文用一个只输出 legacy 的 main.py,对以上两个固定版本实际完成了初始化、导入、运行;真实项目仍应跑原有测试,检查依赖兼容。若旧 requirements.txt 来自 pip freeze,它可能包含几十个间接包。直接导入会把它们都提升为直接依赖,增加后续升级负担。优先整理出代码真正使用的直接包,逐个 uv add;若你有 requirements.in 和已锁定的 requirements.txt,官方提供 uv add -r requirements.in -c requirements.txt,让前者作为直接需求、后者作为迁移时的版本约束。13

暂时只想让 uv 安装旧文件、不迁移项目模型,可以用:

bash 复制代码
uv venv
uv pip install -r requirements.txt

这在 .venv 中安装列表,不会自动把旧项目改造成由 pyproject.toml 和 uv.lock 管理的项目。团队仍须继续维护原来的 requirements。迁移前保存现有可工作的环境与测试输出,迁移后对比功能行为,别把"依赖解析成功"当成业务兼容验证。13

16. requirements.txt 还需要吗?

取决于谁消费依赖文件。团队自己能使用 uv 项目模式时,提交 pyproject.toml 与 uv.lock 就能表达声明和解析结果;某些旧部署平台、已有 Docker 构建流程或只支持 pip 的系统仍可能需要 requirements 文件。不要同时手改两份、让它们悄悄分叉。

如果消费者明确只接受 requirements,可以在项目目录导出:

bash 复制代码
uv export --format requirements.txt --no-dev --output-file requirements.txt

它根据当前项目锁文件写出运行依赖的 requirements 格式;--no-dev 排除测试组;--output-file 避免把文本只打印到终端。导出文件是适配下游的产物,以后依赖变化要重新生成。某些 uv 锁定信息无法无损映射成传统 requirements,是否能满足目标平台仍需在那一平台验证。11

17. uv tool 怎么安装命令行工具?

bash 复制代码
uv tool install ruff
ruff --version

第一行把 Ruff 安装到专门的隔离工具环境,并让 ruff 命令可被 PATH 找到;第二行验证。若命令找不到,按安装器提示更新 PATH 或重启终端。此类用户级、跨多个项目使用 的工具不必每个项目都重复声明;但如果 CI 要检查同一套 Ruff 规则且需要可复现版本,将它作为该项目的 dev 依赖更合适。uv tool install 管理工具自己的环境,不把 Ruff 混入当前项目 .venv。14

18. uvx 是什么?与正式安装有何区别?

bash 复制代码
uvx ruff --version

uvx 是 uv tool run 的简写,临时隔离运行一个提供 CLI 的包。首次可能联网解析、下载;后续可复用缓存,但未指定版本的结果可能随缓存刷新和发布时间变化。它适合试用工具或偶尔执行一次;uv tool install 适合希望该命令长期出现在 PATH 中的用户。需固定检查结果时应固定工具版本并在团队工作流中明确安装/运行方式。14

不要把 uvx 和 uv run 混用:前者针对隔离的 CLI 工具环境,后者运行当前项目环境中的命令。例如本文的 pytest 是 dev 依赖,因此运行 uv run python -m pytest -q。

19. 克隆 uv 项目后怎么运行?哪些文件要上传 GitHub?

对一个已经提交了锁文件的真实仓库:

bash 复制代码
git clone <仓库地址>
cd uv-fastapi-demo
uv sync --locked
uv run --locked python -m pytest -q
uv run --locked uvicorn app.main:app --host 127.0.0.1 --port 8000

git clone 获取项目,cd 进入项目根目录;uv sync --locked 防止静默改写锁文件;测试命令确认 API 的基本契约;最后的 uvicorn 在本机端口 8000 启动服务。<仓库地址> 需要替换成你自己的 Git 地址;本文配套项目在本仓库的 assets/uv-guide-95/uv-fastapi-demo 目录,也可以直接下载资源包。

gitignore 复制代码
.venv/
__pycache__/
*.py[cod]
.pytest_cache/
.ruff_cache/
.coverage
.idea/
.vscode/

提交 源码、测试、pyproject.toml、uv.lock、.python-version、README,以及实际要用于协作的 CI/Docker 配置;忽略 .venv、缓存和本机 IDE 配置。注意有些团队刻意共享 .vscode/settings.json,那时应按团队规则调整忽略项。.venv 不能从一台 Windows 电脑复制给另一台 Linux 机器当作可复现环境。

20. uv + Git 怎么协作?

开发者 A 在项目根目录新增包后:

bash 复制代码
uv add pandas
uv run python -m pytest -q
git add pyproject.toml uv.lock
git commit -m "Add pandas dependency"

先声明并同步,运行测试,再提交声明与锁文件。若项目代码也使用了 pandas,相关源码和测试也应一并提交;这里只突出依赖文件。开发者 B 拉取后:

bash 复制代码
git pull
uv sync --locked

git pull 获取新提交,uv sync --locked 按提交的解析结果同步本机环境,并检测漏提交的锁文件。多人同时修改 uv.lock 时解决冲突应以合并后的 pyproject.toml 为准,重新 uv lock、检查差异、运行测试;不要随意保留两段文本冲突标记。

图 3:团队共享的是源码、声明和锁文件,各自在本机建立 .venv。这能减少版本漂移,但操作系统、架构和系统库仍可能不同。

21. Docker 中如何使用 uv?

下面给出第 27 章配套项目 的最小 Dockerfile;使用 python:3.12-slim 提供解释器,从官方 uv 镜像复制 uv 二进制。15

dockerfile 复制代码
FROM python:3.12-slim
COPY --from=ghcr.io/astral-sh/uv:0.12.22 /uv /uvx /bin/
WORKDIR /app
COPY . /app
ENV UV_PYTHON_DOWNLOADS=0 UV_NO_DEV=1
RUN uv sync --locked
EXPOSE 8000
CMD ["/app/.venv/bin/uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

FROM 选 Python 3.12 运行环境,与 .python-version 对应;COPY --from 使用官方明确提供的镜像路径,uv 版本显式标在镜像标签上;WORKDIR 设置项目根;COPY 导入源码、pyproject.toml 和 uv.lock;两个环境变量分别避免容器内重复下载 Python、排除开发依赖;uv sync --locked 检查锁并安装;EXPOSE 说明应用端口,CMD 用容器内部的环境启动服务。配套 .dockerignore 排除宿主机 .venv,防止把 Windows 环境复制到 Linux 镜像里。

bash 复制代码
docker build -t uv-fastapi-demo .
docker run --rm -p 8000:8000 uv-fastapi-demo

这两条在已安装并运行 Docker 的机器上构建、映射本机 8000 到容器 8000。本篇未在当前环境执行 Docker 构建,因此只将其作为按官方指南核对的示例;需要精确的镜像可复现性时还应固定镜像 digest,并考虑基础镜像更新和系统包安全维护。本文不展开 Docker 全套教程。

22. CI 中如何使用 uv?

CI 的目标是:取代码、安装 uv、按提交的锁文件同步、运行测试。配套项目提供以下 GitHub Actions 工作流;两个 action 的 commit SHA 来自核对时官方 uv 文档示例,版本注释方便识别。16

yaml 复制代码
name: uv demo CI
on:
  push:
  pull_request:
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
      - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
      - run: uv sync --locked
      - run: uv run --locked python -m pytest -q

checkout 获取仓库,setup-uv 安装工具,uv sync --locked 避免 CI 悄悄生成一个新锁文件,最后在项目环境执行测试。配置放在示例项目自身仓库 的 .github/workflows/ci.yml 才会触发;本 CSDN 内容仓库只是配套归档,不能宣称它已经在 GitHub Actions 跑过。团队根据自己的权限和策略维护 action 的固定版本。

23. uv 缓存放在哪里?什么时候需要清理?

uv 可复用下载的元数据、包与构建产物,减少重复获取和构建。缓存不等于项目的 .venv:前者通常跨项目复用,后者是某个项目的实际运行环境。17

bash 复制代码
uv cache dir
uv cache prune

第一行显示当前机器实际缓存位置,避免猜 Windows 或 Linux 的固定路径;第二行清理不再使用的缓存项。单个包缓存确实损坏时可针对该包清理,例如 uv cache clean 包名;uv cache clean 不带包名会清理全部缓存,下一次运行可能重新下载,不应把它当日常"加速"命令。缓存过大先查目录与磁盘占用,再决定是否清理。跨盘、符号链接及 CI 缓存策略可能影响磁盘占用和性能,不写未经测量的倍数。

24. 常见错误怎么排查?

先确认失败发生在找到 uv → 找到 Python → 解析依赖 → 下载构建 → 同步环境 → 执行代码哪一步。下面的命令应在项目根目录运行,只有安装器/PATH 检查例外。

现象 先检查什么 检查命令 下一步
uv 不是可识别的命令 安装器是否成功、终端是否刷新、PATH PowerShell Get-Command uv;Unix command -v uv 重新打开终端,按官方安装页确认实际安装路径
找不到 Python / Python 版本不符 .python-version、requires-python、本机解释器 uv python list --only-installed、uv run python -V 按项目要求 uv python install 3.12;受限网络先核对下载策略
uv add 解析失败 包名、版本约束、Python 范围与平台 uv --version、检查 pyproject.toml 缩小冲突范围,确认包是否支持目标 Python;下载失败另查代理/证书/索引
uv sync --locked 报锁文件过期 声明与锁文件是否同次提交 uv lock --check、git diff -- pyproject.toml uv.lock 由维护者更新锁并提交;CI 不应静默改锁
包支持 3.11,项目却使用 3.13 requires-python 与可用分发是否有交集 uv run python -V、查看解析错误中包的 Requires-Python 选受支持的 Python,或经测试更换兼容版本;不要随意删约束
本机可运行,同学电脑失败 Python、系统库、CPU/OS、锁是否提交 uv --version、uv run python -V、git status 对照错误日志和系统依赖;先 uv sync --locked 再运行测试
.venv 损坏或指向已删除解释器 环境能否重建 uv run python -c "import sys; print(sys.executable)" 在确认未存放需要的数据后删除项目 .venv,再 uv sync --locked
IDE 显示包不存在 IDE 是否用了项目解释器 uv run python -c "import sys; print(sys.executable)" VS Code 的 Python: Select Interpreter 或 PyCharm 项目解释器选 .venv 中 Python
构建 wheel 失败 编译工具、系统头文件或目标平台 wheel 阅读完整 build failure 日志 补齐系统依赖、选兼容发行版;不能把所有失败都归因于 uv

PowerShell 删除确实损坏的本地虚拟环境可用 Remove-Item -Recurse -Force .venv;macOS/Linux 用 rm -rf .venv。先确认当前位置就是目标项目 ,并确保 .venv 中没有人为放置的唯一数据。删除缓存与删除环境是两种不同操作。

25. uv 与 Conda 是什么关系?

Conda 的环境模型还覆盖不少非 Python 二进制依赖、编译栈和科学计算/硬件相关包;uv 的主要工作流围绕 Python 解释器、Python 包和项目元数据。对纯 Python Web 服务或一般自动化脚本,uv 项目文件和锁文件通常能把流程做得简洁;对依赖特定 CUDA、系统库、数值计算二进制栈的项目,Conda 或系统包管理器仍可能是合理组成部分。

关键是列清项目外的依赖由谁管理 。即使使用 Conda 管好 CUDA,Python 项目内部依赖如何声明和协作仍要设计;反过来,仅用 uv 无法把驱动版本装进 uv.lock。没有必要做"彻底替代"的宣告。

26. uv 与 Poetry 如何理解?

Poetry 与 uv 都可管理 Python 项目的依赖声明、锁定及运行,但锁文件格式、默认项目流程、Python 管理能力和工具范围不同。已经在 Poetry 下运行稳定的项目,应先看现有 pyproject.toml、锁文件、插件和 CI,再比较迁移收益与团队学习成本。不能把 Poetry 的锁文件改名为 uv.lock 就交给 uv;需要重新解析并测试行为。

若新项目需要 Python 安装、环境、应用依赖、运行命令和 CLI 工具都由同一工具处理,uv 是可选方案。若团队现有流程有成熟的发布/构建约定,继续使用 Poetry 也合理。选择标准应是项目需求、团队协作、可重建与可测试性,而不是工具营销中的排名。

27. 从零完成一个可复现的 FastAPI 项目

本例实现一个内存中的任务 API:健康检查、创建、查询、删除,并用 pytest 检查正常与错误路径。数据只存于进程内存,重启即丢失;其目的在于验证依赖管理链路,不用数据库复杂性转移主题。运行版本与依赖来自本次真实创建和锁定的配套项目。

27.1 初始化、添加运行与开发依赖

在一个没有同名目录的工作区执行:

bash 复制代码
uv init --no-package --python 3.12 uv-fastapi-demo
cd uv-fastapi-demo
uv add fastapi uvicorn
uv add --dev pytest httpx

--no-package 表示此例不用构建/安装自身 Python 包,生成顶层 main.py;本例把代码放进 app/ 后删掉这个初始 main.py。--python 3.12 设置项目初始 Python 请求并生成 .python-version;uv add fastapi uvicorn 加入运行依赖,uv add --dev pytest httpx 加入测试依赖。命令在 uv 0.12.19 本机成功执行。若你的工作区处于另一个 uv workspace 下,先选择独立目录,避免 uv init 意外把项目纳入上层 workspace。6

最终目录:

text 复制代码
uv-fastapi-demo/
├── .github/workflows/ci.yml
├── .dockerignore
├── .gitignore
├── .python-version
├── app/
│   ├── __init__.py
│   └── main.py
├── tests/
│   └── test_app.py
├── Dockerfile
├── README.md
├── pyproject.toml
└── uv.lock

.venv/ 会在本机生成,但已忽略,不包含在分享目录里。README.md 说明运行命令;uv.lock 是真实解析结果,全文展示它会掩盖最重要的项目代码,完整文件在配套资源中。

27.2 完整配置与 API 源码

pyproject.toml 是本次 uv add 后的完整依赖声明:

toml 复制代码
[project]
name = "uv-fastapi-demo"
version = "0.1.0"
description = "A small task API for learning the uv project workflow"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
    "fastapi>=0.142.2",
    "uvicorn>=0.54.0",
]

[dependency-groups]
dev = [
    "httpx>=0.28.1",
    "pytest>=9.1.1",
]

app/__init__.py 可以是空文件;配套版本保留一句模块说明。app/main.py:

python 复制代码
from itertools import count

from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel, Field


class TaskCreate(BaseModel):
    title: str = Field(min_length=1, max_length=100)


class Task(BaseModel):
    id: int
    title: str
    done: bool = False


def create_app() -> FastAPI:
    api = FastAPI(title="uv FastAPI demo")
    tasks: dict[int, Task] = {}
    next_id = count(1)

    @api.get("/health")
    def health() -> dict[str, str]:
        return {"status": "ok"}

    @api.post("/tasks", response_model=Task, status_code=status.HTTP_201_CREATED)
    def create_task(payload: TaskCreate) -> Task:
        task = Task(id=next(next_id), title=payload.title)
        tasks[task.id] = task
        return task

    @api.get("/tasks/{task_id}", response_model=Task)
    def get_task(task_id: int) -> Task:
        task = tasks.get(task_id)
        if task is None:
            raise HTTPException(status_code=404, detail="task not found")
        return task

    @api.delete("/tasks/{task_id}", status_code=status.HTTP_204_NO_CONTENT)
    def delete_task(task_id: int) -> None:
        if tasks.pop(task_id, None) is None:
            raise HTTPException(status_code=404, detail="task not found")

    return api


app = create_app()

TaskCreate 约束标题长度,Task 是响应数据形状;create_app 每次建立独立的内存字典与 ID 计数器,便于测试隔离。POST /tasks 创建并返回 201;GET /tasks/{task_id} 查不到返回 404;DELETE 成功返回 204。服务重启后计数器与任务字典重新开始,这不是持久化方案。

27.3 测试代码、启动与实际响应

tests/test_app.py:

python 复制代码
from fastapi.testclient import TestClient

from app.main import create_app


def test_health() -> None:
    with TestClient(create_app()) as client:
        response = client.get("/health")
    assert response.status_code == 200
    assert response.json() == {"status": "ok"}


def test_create_read_delete() -> None:
    with TestClient(create_app()) as client:
        created = client.post("/tasks", json={"title": "learn uv"})
        assert created.status_code == 201
        assert created.json() == {"id": 1, "title": "learn uv", "done": False}

        found = client.get("/tasks/1")
        assert found.status_code == 200
        assert found.json() == created.json()

        deleted = client.delete("/tasks/1")
        assert deleted.status_code == 204
        assert client.get("/tasks/1").status_code == 404


def test_invalid_input_and_missing_task() -> None:
    with TestClient(create_app()) as client:
        assert client.post("/tasks", json={"title": ""}).status_code == 422
        assert client.get("/tasks/999").status_code == 404

每个测试调用 create_app(),避免测试之间共享上一轮内存数据。TestClient 用 HTTP 风格的请求检查状态码和 JSON;httpx 是这个测试客户端需要的开发依赖。用下面两条命令:

bash 复制代码
uv run --locked python -m pytest -q
uv run --locked uvicorn app.main:app --reload --host 127.0.0.1 --port 8000

先运行测试,再另开终端启动服务。--locked 在锁文件不匹配时立即报错,--reload 只用于本机开发,127.0.0.1 不对外暴露。本次 Linux 运行测试为 3 passed;当前 FastAPI/Starlette 组合出现一条关于测试客户端使用 httpx 的上游弃用提示,不影响三项断言通过,详见验证记录。18

第三个终端用 curl 验证真实 HTTP:

bash 复制代码
curl http://127.0.0.1:8000/health
curl -X POST http://127.0.0.1:8000/tasks -H "Content-Type: application/json" -d '{"title":"learn uv"}'
curl http://127.0.0.1:8000/tasks/1
curl -X DELETE -i http://127.0.0.1:8000/tasks/1

第一条请求应得到 {"status":"ok"};第二条发送 JSON 并获得任务 ID,第三条按 ID 查询,第四条显示 204 且没有响应内容。以上 curl 单引号 JSON 适合 macOS/Linux/WSL ;在 Windows PowerShell 中可用 Invoke-RestMethod -Uri http://127.0.0.1:8000/tasks -Method Post -ContentType 'application/json' -Body '{"title":"learn uv"}' 创建任务;其他 GET 可用 Invoke-RestMethod,不要把 Linux 的引号规则直接复制到 PowerShell 并期待所有版本一致。

27.4 Git 提交、新机器恢复与清理

在示例项目根目录执行:

bash 复制代码
git init
git add .
git status --short
git commit -m "Create uv FastAPI demo"

git init 新建仓库,git add . 暂存未被忽略的项目文件,git status --short 让你检查 .venv 没混入,最后一次提交源码、配置和锁文件。真实推送需要你自己的 GitHub 仓库 URL;本文不编造一个可克隆的示例地址。把项目克隆到其他目录后,运行 uv sync --locked,再执行测试,即可验证声明与锁文件是否能重建项目环境。

上传自己的 GitHub 空仓库时,在 GitHub 创建好目标仓库、复制其真实 HTTPS 地址,然后在本地项目继续:

bash 复制代码
git branch -M main
git remote add origin <你的GitHub仓库HTTPS地址>
git push -u origin main

第一行统一当前分支名,第二行把真实远端设为 origin,第三行推送并建立上游关联。若远端初始化时已带 README、已有不同历史,应先按 Git 提示拉取并合并,别对不属于自己的分支强推。上传的是源码与锁文件;.venv/ 被 .gitignore 排除。本文只在临时本地 Git 仓库验证了提交与克隆,没有替读者创建示例 GitHub 仓库或执行远端推送。

想演示依赖变化,可以在临时练习项目 执行 uv add requests,观察两个文件改动,再 uv remove requests 并重新检查锁与测试;正式仓库不需留下未使用的依赖。停止本地 uvicorn 用 Ctrl+C;清除内存任务仅需停止进程。需要删 .venv 时按第 24 章确认路径后重建,无须把它打进 ZIP 或 Git。

28. uv 适合什么项目?

Python Web 服务、CLI、数据处理脚本和一般 AI 应用,只要主要依赖能够通过 Python 包索引与明确的系统前提安装,都可从"解释器选择---声明---锁定---同步---运行"的统一工作流获益。对只写几行、一次性运行的脚本,未必需要完整项目;对 Python 包本身的发布,还需保留正确的构建后端、包布局和发布验证。

考虑是否采用 uv 时,先问:项目需要团队复现吗?当前 requirements 是手写直接依赖还是 pip freeze?目标平台是什么?CI/部署方能不能消费锁文件?是否有必须通过系统包或 Conda 处理的二进制依赖?这些答案比"它是否最快"更影响工程选择。

29. uv 不应该被神化:下一步怎么维护?

uv 可以减少 Python 环境管理的重复工作,但不能替你完成操作系统依赖安装、CUDA 驱动匹配、全部平台上的二进制兼容验证、凭据治理和业务测试。锁文件缩小了依赖版本的不确定性;测试、CI、系统环境记录补齐其余证据。极简单机脚本可继续使用标准库和现有工具,项目复杂度应服务于协作需要。

本文完整链路是:选择 Python → 建项目与虚拟环境 → 声明直接依赖 → 提交锁文件 → 同步新环境 → 在环境中运行与测试。下一步可根据项目实际需要学习构建发布、依赖升级策略、CI 缓存和 Docker 分层构建;这些属于下一轮工程实践,不必在第一个 uv 项目里一次堆齐。

官方资料与验证边界

以下链接于 2026-10-02 核对;uv 文档与依赖版本会更新。文章中的图为自绘说明图,官方来源用于验证命令与行为,不将图当作官方截图。

  1. uv 功能与起步
  2. Python 标准库 venv
  3. uv Python 版本管理
  4. uv 项目依赖管理
  5. uv 官方安装
  6. uv 创建项目与模板
  7. Python Packaging:pyproject.toml 规范
  8. Python Packaging:Dependency Groups 规范
  9. uv 锁定与同步
  10. uv 锁文件
  11. uv 导出锁文件
  12. uv 运行项目命令
  13. 官方 pip → uv 项目迁移指南
  14. uv 管理 CLI 工具
  15. uv Docker 指南
  16. uv GitHub Actions 指南
  17. uv 缓存
  18. 配套项目的实际验证记录
相关推荐
打工仔折腾 AI1 小时前
DiPlay 实测:iPhone 绕过硬件盒子直连 BYD 车机的思路拆解
android·人工智能·后端·python·gradle·iphone·ai agent 实战
東隅已逝,桑榆非晚1 小时前
c++模板进阶
开发语言·c++·笔记·学习
老歌老听老掉牙5 小时前
空间曲线数据的拼接与几何变换分析
python·曲线
Cc.Y6 小时前
Java零基础入门:字符串深度掌握——从基础API到StringBuilder性能优化
java·开发语言·性能优化
databook9 小时前
使用 SciPy 库进行时间序列分析
python·数据分析·scipy
java资料站10 小时前
十一、评估测试
开发语言·人工智能·python
szial10 小时前
网络爬虫与 CDP 实战(二):浏览器能访问,Python 却被拒绝?拆开登录态与 CSRF
爬虫·python·csrf
左手の明天10 小时前
SLIP 协议封装与解析详解:原理、C 代码与实战案例
linux·c语言·开发语言·c++
海上小飞龙10 小时前
【KMP算法-下篇】同一道题:Java 库函数 2600 微秒,手写 KMP 16 微秒
java·开发语言·算法