https://docs.astral.sh/uv/guides/scripts/
1. MECE 原则拆解
为了系统性地理解 uv 在脚本运行方面的功能,我们可以将文章内容拆解为以下几个相互独立、完全穷尽的模块:
- 核心问题 (The “Why”):
uv旨在解决什么传统痛点? - 核心理念 (The “What”):
uv提出的解决方案是什么? - 功能实现 (The “How”):
uv是如何通过具体功能实现其理念的?- A. 脚本执行: 如何运行不同类型的脚本。
- B. 依赖管理: 如何声明和管理脚本的依赖。
- C. 环境管理: 如何处理 Python 版本和包索引。
- D. 可复现性: 如何保证脚本在不同时间、不同环境下行为一致。
- E. 集成与分发: 如何让脚本更像一个独立的可执行程序。
- 本质思考:
uv这种模式的本质是什么? - 实践建议: 下一步可以做什么?
2. 逐一解析
1. 核心问题:传统 Python 脚本的痛点
在没有 uv 之前,运行一个带依赖的 Python 脚本通常很繁琐:
- 手动环境管理: 你需要手动创建虚拟环境 (
python -m venv .venv),激活它 (source .venv/bin/activate),然后安装依赖 (pip install -r requirements.txt),最后才能运行脚本 (python script.py)。 - 依赖与代码分离: 依赖项通常写在
requirements.txt文件里,与脚本本身是分离的,分发和管理不便。 - 隐式依赖: 一个项目中的
requirements.txt可能包含整个项目的所有依赖,而你的脚本可能只需要其中一小部分,造成了环境臃肿。
2. 核心理念:脚本即环境,声明式运行
uv 的核心理念是 “将环境管理和脚本运行合二为一”。
它推崇一种声明式的方法:让脚本文件自身就包含运行它所需要的所有信息(如依赖包、Python 版本)。当你使用 uv run 时,uv 会在背后为你完成所有繁琐工作:
- 读取脚本中的元数据(或命令行参数)。
- 在缓存中快速创建一个隔离的、临时的虚拟环境。
- 安装声明的依赖。
- 执行脚本。
- 执行完毕,这个临时环境几乎没有存在感。
这个过程是原子化的,极大地简化了工作流。
3. 功能实现:uv 的“工具箱”
A. 脚本执行 (Running Scripts)
- 无依赖脚本: 最简单的场景,直接运行。
uv run simple_script.py - 带参数脚本: 和
python命令一样传递参数。uv run script_with_args.py "hello world" - 从标准输入读取: 可以通过管道将代码传给
uv。echo 'print("hello from stdin")' | uv run - - 与项目环境隔离: 如果你在一个有
pyproject.toml的项目目录中,uv默认会安装项目依赖。使用--no-project可以忽略项目,只为当前脚本创建纯净环境。# 即使在项目目录里,也只为 my_script.py 创建独立环境 uv run --no-project my_script.py
B. 依赖管理 (Dependency Management)
这是 uv 最强大的功能之一。它提供了两种声明依赖的方式:
-
临时指定 (Ad-hoc): 使用
--with参数,适合一次性运行或测试。# 临时为脚本安装 rich 和 requests 包 uv run --with rich --with "requests<3" my_script.py -
内联元数据 (Inline Metadata - 推荐): 遵循 PEP 723 规范,将依赖直接写在 Python 脚本的注释里。
uv提供了命令来帮你管理这个元数据块。- 初始化脚本:
uv init --script my_script.py - 添加/更新依赖:
uv add --script my_script.py rich "requests<3"
这会在
my_script.py的顶部生成或更新一个/// script块:# /// script # dependencies = [ # "rich", # "requests<3", # ] # /// import requests from rich import print # ... 你的代码 ...当你用
uv run my_script.py运行时,uv会自动读取这个块并安装依赖。这是uv实现“脚本即环境”理念的关键。 - 初始化脚本:
C. 环境管理 (Environment Management)
- Python 版本: 你可以在元数据中或命令行中指定 Python 版本,
uv会自动寻找或下载安装。- 元数据中:
# /// script # requires-python = ">=3.10" # dependencies = [] # /// - 命令行中:
uv run --python 3.11 my_script.py
- 元数据中:
- 自定义包索引: 如果你使用私有 PyPI 仓库,可以通过
--index指定。uv add --script --index "https://private.repo/simple" my_script.py my-private-package
D. 可复现性 (Reproducibility)
- 依赖锁定 (Locking): 为了确保每次运行使用的依赖版本完全一致,可以为脚本创建一个锁文件。
之后# 这会生成一个 my_script.py.lock 文件 uv lock --script my_script.pyuv run会优先使用这个锁文件,确保了依赖的确定性。 - 时间旅行 (Time-based Reproducibility): 这是一个非常精妙的功能。你可以通过
exclude-newer字段,告诉uv忽略某个时间点之后发布的包版本,这能极大地防止未来某个依赖的更新破坏你的脚本。# /// script # dependencies = ["requests"] # [tool.uv] # exclude-newer = "2023-10-16T00:00:00Z" # 忽略此日期后发布的 requests 版本 # ///
E. 集成与分发 (Integration & Distribution)
- 创建可执行脚本 (Shebang): 你可以在脚本顶部使用
shebang,结合uv run,让你的 Python 脚本变成一个可以直接在 Shell 中运行的命令。
保存为#!/usr/bin/env -S uv run --script # /// script # dependencies = ["rich"] # /// from rich import print print("Hello, [bold green]executable[/bold green] world!")greet,然后chmod +x greet,之后就可以直接./greet运行了。这对于编写命令行工具非常有用。
4. 本质思考:这玩意儿的本质是什么?
uv 在脚本运行方面的本质是 “为 Python 脚本提供了类似 Docker 的运行时体验”。
我们可以做一个类比:
| Docker | uv for Scripts | 描述 |
|---|---|---|
Dockerfile | 内联元数据 /// script | 声明式定义:定义运行所需的环境和依赖。 |
docker build | uv lock / 第一次 uv run | 环境构建:解析依赖,创建并缓存一个确定的环境。 |
docker run | uv run | 原子化执行:在一个命令中完成环境准备和代码执行。 |
| Docker Image | uv 的全局缓存 | 不可变和可缓存:构建好的环境被缓存,下次运行极快。 |
uv 将 Python 脚本从一个单纯的“代码文件”提升为了一个“自包含、可执行的软件单元”。它抽象掉了虚拟环境的底层细节,让开发者更专注于代码逻辑本身。
5. 实践建议:下一步做什么?
- 安装
uv: 如果还没安装,立刻去安装它。 - 改造旧脚本: 找一个你以前写的、带
requirements.txt的小项目或脚本。- 尝试用
uv run --with <package>运行它。 - 然后使用
uv add --script将依赖内联到脚本中,并删除requirements.txt。感受一下代码和依赖在一起的清爽感。
- 尝试用
- 编写一个新工具: 写一个新的命令行小工具,比如一个能查询天气或处理文本的脚本。
- 从
uv init --script开始。 - 使用
shebang(#!/usr/bin/env -S uv run --script) 让它变成可执行文件。 - 把它放到你的
PATH路径下,体验像原生命令一样使用它的感觉。
- 从
- 探索可复现性: 对一个脚本使用
uv lock,看看生成的.lock文件。然后尝试修改脚本中的依赖声明,再次运行uv run,观察uv是如何提示你锁文件过期的。这能加深对依赖确定性的理解。
716

被折叠的 条评论
为什么被折叠?



