Chapter 06

虚拟环境与 uv run

从此告别 source venv/bin/activate——uv run 让激活变成过去时

uv venv — 极速创建虚拟环境

即使你不想用完整的项目模式(uv init),uv 也可以纯粹当一个"超快的 venv 工具"用:

# 默认创建到 .venv
uv venv

# 指定路径
uv venv myenv
uv venv ~/.virtualenvs/playground

# 指定 Python 版本(自动下载)
uv venv --python 3.13
uv venv --python 3.12.4

# 不基于项目的 pyproject,自定义 requires
uv venv --python 3.13 --prompt mycli
# shell 激活后提示符显示 (mycli)

性能对比:

工具创建空 venv 耗时
python -m venv~1.2s
virtualenv~0.6s
uv venv~0.015s

venv 里有什么?

.venv/
├── bin/              # Linux/macOS;Windows 是 Scripts/
│   ├── activate       # 传统激活脚本(bash)
│   ├── activate.fish
│   ├── python3        # 软链到实际 Python 解释器
│   └── pip            # 虽然 uv 不用 pip,它仍被安装
├── lib/python3.13/site-packages/   # 装的包在这里
└── pyvenv.cfg        # 记录 Python 路径、venv 类型

激活 vs 免激活

传统工作流:

source .venv/bin/activate     # 激活
python main.py                 # 用 venv 里的 python
deactivate                     # 用完记得退出

uv 工作流(推荐):

uv run python main.py          # 直接跑,不激活
uv run pytest                  # 跑 venv 里的 pytest
uv run -- ruff check .          # -- 后面是原命令(避免 ruff 的参数被 uv 解释)
为什么推荐免激活?

零记忆负担:不会忘记是否激活了正确环境。② 跨项目安全:在项目 A 的 shell 里敲 uv run 自动用 A 的 venv,即使你刚从项目 B 的激活环境切过来。③ IDE 友好:VS Code 的 Python 扩展本就能自动识别 .venv,无需激活。

uv run 的魔法

uv run 在执行命令前会自动:

  1. 查找项目根(向上找 pyproject.toml)。
  2. 确保所需 Python 版本已安装(没装会下载)。
  3. 确保 .venv 存在且与 uv.lock 一致(不一致会自动 sync)。
  4. 激活该 venv 的 PATH 和环境变量。
  5. 执行你的命令。
# 常用用法
uv run python script.py
uv run pytest tests/
uv run jupyter lab
uv run uvicorn app:main --reload

# 不自动 sync(提高启动速度,假设环境已对)
uv run --no-sync python main.py

# 以特定组的依赖运行
uv run --group docs mkdocs serve
uv run --all-extras pytest

# 传环境变量
uv run --env-file .env python main.py
uv run DEBUG=1 python main.py       # 行内变量

PEP 723 — 单文件脚本的依赖

经常有这样的场景:你想写一个 60 行的小工具脚本依赖 requests 和 click,但不想创建完整项目。传统做法是全局装依赖污染系统,或者搞个 venv 显得小题大做。

PEP 723 允许把依赖写在脚本开头的特殊注释里:

# /// script
# requires-python = ">=3.12"
# dependencies = [
#   "httpx",
#   "rich",
# ]
# ///

import httpx
from rich import print

r = httpx.get("https://api.github.com")
print(r.json())
# uv 会自动解析这段元数据,创建临时 venv,装依赖,跑脚本
uv run my_script.py

# 生成这段注释骨架
uv init --script my_script.py

# 给脚本添加依赖(自动更新注释)
uv add --script my_script.py rich httpx

# 指定 Python 版本
uv run --python 3.13 my_script.py
PEP 723 适用场景

运维脚本:部署、备份、数据迁移的一次性工具。② CLI 小工具:想分享给同事的"chmod +x 直接跑"脚本。③ 教学示例:博客和 README 里贴代码,读者一条 uv run 即可复现。

shebang — 让脚本可直接执行

#!/usr/bin/env -S uv run --script
# /// script
# dependencies = ["rich"]
# ///
from rich import print
print("[bold cyan]Hello from a portable Python tool![/]")
chmod +x hello.py
./hello.py    # 直接跑,uv 自动处理依赖

环境变量与 dotenv

uv 原生支持 .env 文件(需要 --env-file):

# .env
DATABASE_URL=postgres://localhost/mydb
OPENAI_API_KEY=sk-...

# 运行时加载
uv run --env-file .env python main.py

# 或在 pyproject 里永久配置
[tool.uv]
env-file = ".env"

venv 的迁移性

.venv 不可跨机器复制

.venv 里的 Python 解释器是软链到你机器上某个绝对路径的,直接打包发给别人不能用。正确做法:把 pyproject.toml + uv.lock + .python-version 提交到 git,别人 uv sync 即可获得一模一样的环境。

uv pip — pip 兼容接口

某些老教程、老 CI 脚本只会 pip install xxx。uv 提供了一个完全兼容 pip 命令行的子命令:

uv pip install requests
uv pip install -r requirements.txt
uv pip list
uv pip freeze
uv pip uninstall requests
uv pip compile requirements.in -o requirements.txt   # pip-tools 替代

注意uv pip 是兼容层,不会动 pyproject.toml 和 uv.lock。在项目模式里永远用 uv adduv pip 只在非项目场景(快速试用某包、兼容遗留脚本)使用。

本章小结

uv venv 创建环境比 venv 快 80 倍,uv run 让"激活"成为历史,PEP 723 让单文件脚本也能管依赖。养成习惯:任何 Python 命令都用 uv run ... 开头,从此跨项目切换零心智负担。下一章讲 uv tool——全局安装 CLI 工具的现代方式。