# uv
[](https://hackmd.io/@RogelioKG/uv)
## References
+ 📑 [**Documentation - uv**](https://docs.astral.sh/uv/)
+ 🔗 [**使用 uv 管理 Python 環境**](https://dev.to/codemee/shi-yong-uv-guan-li-python-huan-jing-53hg)
+ 🎬 [**ArjanCodes - UV for Python… (Almost) All Batteries Included**](https://youtu.be/qh98qOND6MI)
## Installation
+ Windows
```bash
scoop install uv
```
## Cheatsheet
👇 最常用到的指令

## Advantages
🐍 Python 終於也有了一個上得了檯面的 package manger
### 🧩 良好的依賴解析
> 刪除套件時,是真的會找出沒用到的依賴套件並刪除 (確保無 redundancy)
### 🧰 不只是依賴管理
> 集成非常多 out-of-the-box 的工具
> + Dependency Management
> + Virtual Environment
> + Multi-version Python
> + Publish Packages
### ⚡ 比 pip 快<mark>數十倍</mark>的速度
> 你以為 package manager 安裝套件的耗時,就是讓你去泡咖啡偷懶的時間嗎?\
> 噢不我的朋友,當你拿著你的杯子,準備離開電腦桌的時候,\
> uv 就已經以趕火車的速度,完成 dependency resolution 並 install 完畢。\
> 想見識這個驚掉下巴的速度,詳見 [benchmark](https://github.com/astral-sh/uv/blob/main/BENCHMARKS.md)。
## Note
|📘 <span class="note">NOTE</span> : uv|
|:---|
|uv 仍在開發中!<br><span style="color: grey;">註:此筆記紀錄的是 `0.9.5` (2025/10/21) 的功能!</span>|
| uv 安裝第三方套件的方式:使用 hardlink (詳見:[cache](#cache:快取))|
|uv 的 <mark>[build backend](https://hackmd.io/@RogelioKG/setuptools):`uv-build`</mark> (打包成可發布套件的工具)|
|uv 的 <mark>lockfile:`uv.lock`</mark> (紀錄每個套件的版本與它們的依賴關係) <br><span style="color: grey;">註:PEP 751 (2024/7/26) 終於正式要求了 Python 的標準 lockfile 為 `pylock.toml`。</span>|
## Resolving
| 類型 | 套件共用策略 | 套件多版本共存 | 範例 |
| --- | --- | --- | --- |
| **Tree** | 不共用 | ✅ 允許 | `npm v2` |
| **Partial Graph** | 儘量共用,衝突時允許多版本 | ✅ 允許 | `npm v3+` / `pnpm` (peer dependency 衝突) |
| **Full Graph** | 所有套件,共用唯一版本 | ❌ 不允許 | `uv` / `pip` / `pnpm` |
> Full Graph 版本求解,本質上是一個 SAT 問題,\
> 通常採用 SAT solver 來嘗試找解。\
> uv 使用的是一種特別的 solver:[PubGrub](https://github.com/pubgrub-rs/pubgrub)。
## Commands
詳見 [UV CLI](https://docs.astral.sh/uv/reference/cli/#uv)。
### `init`:創建專案
+ #### `--python`
> 指定 Python 版本
+ #### `--script`
> 用於構建一個簡單<mark>腳本</mark>
+ 腳本的所有依賴直接寫在 dependencies
```py
# /// script
# requires-python = ">=3.13"
# dependencies = ["httpx"]
# ///
import httpx
def main():
with httpx.Client() as client:
response = client.get("https://fakestoreapi.com/products/1")
print("Status Code:", response.status_code)
print("Response JSON:", response.json())
if __name__ == "__main__":
main()
```
+ 執行時,自動安裝所有依賴 (若有快取,會自動使用)
```
uv run main.py
```
+ 未在 dependencies 指定的依賴,可外加 `--with` 選項新增依賴
> 假設你想換成某個版本的 httpx
```
uv run --with httpx==0.27.0 main.py
```
+ #### `--app`
> 用於構建一個<mark>應用程式</mark>,通常不作為套件發布
+ 目錄架構
```
project_app/
├── .gitignore
├── .python-version
├── main.py
├── pyproject.toml
└── README.md
```
+ #### `--lib`
> 用於構建一個<mark>函式庫</mark>,可作為套件發布
+ 目錄架構
```
project_lib/
├── .gitignore
├── .python-version
├── pyproject.toml
├── README.md
└── src/
└── project_lib/
├── py.typed
└── __init__.py
```
+ `pyproject.toml` (build 相關資訊儲存於此)
```toml
[project]
...
[build-system]
requires = ["uv_build>=0.9.3,<0.10.0"]
build-backend = "uv_build"
```
+ `uv sync` 測試安裝
+ 在專案內「安裝」你寫的這個套件,你可以一邊開發、一邊測試功能 (如同 `pip install -e .`)
+ 原理:把 `src/` 目錄加入 `sys.path`
+ user 實際安裝
+ 只有 `src/` 目錄中的內容,會被放入 `.venv/Lib/site-packages/` 目錄
+ #### `--package`
> 用於構建一些 <mark>CLI 工具</mark>,可作為套件發布
+ 目錄架構
```
project_package/
├── .gitignore
├── .python-version
├── pyproject.toml
├── README.md
└── src/
└── project_package/
└── __init__.py
```
+ `pyproject.toml` (build 相關資訊儲存於此)
```toml
[project.scripts]
rogeliokg-core = "rogeliokg_core:main"
# 執行檔名稱 rogeliokg-core
# 入口函式 src/rogeliokg_core/__init__.py:main
[build-system]
requires = ["uv_build>=0.9.3,<0.10.0"]
build-backend = "uv_build"
```
+ `uv sync` 測試安裝
+ 在專案內「安裝」你寫的這個套件,你可以一邊開發、一邊測試功能 (如同 `pip install -e .`)
+ 原理:把 `src/` 目錄加入 `sys.path`
+ user 實際安裝
+ 只有 `src/` 目錄中的內容,會被放入 `.venv/Lib/site-packages/` 目錄
+ <mark>會在 `.venv/Scripts/` 生成執行檔,供 user 調用</mark> (註:需先進入 venv)
+ #### `--build-backend`
> 指定打包用 backend\
> (若專案是可發布套件才會用的到)
+ 可自己指定第三方 build backend
> 比如:`hatch` (hatchling)、`setuptools` (setuptools)。\
> 預設是 `uv-build`。
### `sync`:同步套件
> <mark>安裝全部套件的加強版</mark>。\
> 假如發生一些意外,導致你的 venv 缺了某些套件,\
> 或者 lockfile 不小心掉入垃圾桶,都可以用這個同步重新長回來。
### `add` :安裝套件
|📗 <span class="tip">TIP</span>|
|:---|
|有 wheel 包的話,也可以直接安裝:`uv add ???.whl`|
|🚨 <span class="caution">CAUTION</span>|
|:---|
|在某些 OS 中,點擊 wheel 檔是有可能直接安裝的<br>(<mark>傳給別人前請務必詳細說明,不要直接點擊!</mark>)|
+ #### `-r`
> 使用 `requirements.txt` 安裝套件,並更新 `pyproject.toml` 和 `uv.lock`
```
uv add -r requirements.txt
```
|🚨 <span class="caution">CAUTION</span>|
|:---|
| 若想用同事給的 `requirements.txt`,使用 uv 安裝套件,只能把所有套件都安裝進來。<br><mark>無法得知同事原先使用 `pip install` 哪些套件</mark>。 |
+ 使用 pip (對它而言是<mark>同一個結構</mark>)
+ 安裝 `requests`
```
certifi==2025.10.5
charset-normalizer==3.4.4
idna==3.11
requests==2.32.5
urllib3==2.5.0
```
+ 安裝 `urllib3` 和 `requests`
```
certifi==2025.10.5
charset-normalizer==3.4.4
idna==3.11
requests==2.32.5
urllib3==2.5.0
```
+ 使用 uv (對它而言卻是<mark>不同結構</mark>)
+ 安裝 `requests`
```
temp v0.1.0
└── requests v2.32.5
├── certifi v2025.10.5
├── charset-normalizer v3.4.4
├── idna v3.11
└── urllib3 v2.5.0
```
+ 安裝 `urllib3` 和 `requests`
> 注意到了嗎,你手動安裝的套件,會在頂層。
```
temp v0.1.0
├── requests v2.32.5
│ ├── certifi v2025.10.5
│ ├── charset-normalizer v3.4.4
│ ├── idna v3.11
│ └── urllib3 v2.5.0 (*)
└── urllib3 v2.5.0
```
|🚨 <span class="caution">CAUTION</span>|
|:---|
| 既然一種扁平狀結構,能推斷出多種樹狀結構,<br>我們就無法用一種扁平狀結構,去唯一決定為一種樹狀結構。<br>意即:無法猜測同事原先使用 `pip install` 哪些套件。<br><mark>這絕不是 uv 的缺陷,恰恰相反,這是 pip 的缺陷!</mark> |
```toml
# 使用 `requiremets.txt` 安裝後,會長成這副慘烈的模樣
[project]
...
dependencies = [
"certifi==2025.10.5",
"charset-normalizer==3.4.4",
"idna==3.11",
"requests==2.32.5",
"urllib3==2.5.0",
]
```
|📗 <span class="tip">TIP</span>|
|:---|
|那要怎麼解決呢?<br>只能<mark>請你的同事回想,他當初手動安裝過的是哪些套件</mark>囉!|
+ #### `--no-sync`
> 不自動同步
+ 只解析依賴,並更新 `pyproject.toml` 和 `uv.lock`
+ 不會自動安裝、移除套件
### `remove`:移除套件
> 移除套件時,會自動解析並移除未使用的依賴套件。
+ #### `--no-sync`
> 不自動同步
+ 只解析依賴,並更新 `pyproject.toml` 和 `uv.lock`
+ 不會自動安裝、移除套件
### `run`:執行腳本
+ #### ` `
> 一般執行
```
uv run main.py
```
+ #### `--with`
> 暫時將某版本的套件加入環境並執行。
```
uv run --with httpx==0.26.0 main.py
```
|🚨 <span class="caution">CAUTION</span>|
|:---|
|會下載到快取,若太久沒清會很胖,要定期清|
+ #### `--python`
> 暫時使用某版本 Python 執行
```
uv run --python 3.13.7 main.py
```
|📗 <span class="tip">TIP</span>|
|:---|
|開發套件時,拿來多版本測試超好用!|
### `tree`:依賴樹
> 展示套件們的依賴關係
### `python`:多版本
+ #### `list`
> 列出可用 Python 版本
```
uv python list
```
+ #### `install` / `uninstall`
> 安裝 / 移除
```
uv python install 3.12.0
```
|📘 <span class="note">NOTE</span>|
|:---|
|Python 直譯器會被放在 `~/.local/bin` (全域可見)|
|<mark>在全域可使用 `python3.xx` 把 REPL 互動式環境叫出來</mark>|
+ #### `pin`
> 切換 Python 版本 (更改 `.python-version`)
+ ` `
> 專案內的 Python 版本
```
uv python pin 3.11
```
+ `--global`
> 之後 init 專案的 Python 版本
```
uv python pin 3.11 --global
```
### `export`:將 lockfile 導出為其他格式
+ #### ` `
```
uv export --no-hashes --format requirements-txt > requirements.txt
```
|🚨 <span class="caution">CAUTION</span>|
|:---|
|當 `pyproject.toml` 和 `uv.lock` 不一致時,<br><mark>試圖同步</mark>,再導出 `uv.lock` 的 lockfile 資訊。|
+ #### `--format`
> 輸出格式
+ #### `--no-hashes`
> 不希望導出內容有 hash 值。\
> (hash 值確保你下載到的是原本的套件,能防止供應鏈攻擊、中間人攻擊)
+ #### `--frozen`
> 當 `pyproject.toml` 和 `uv.lock` 不一致時,\
> <mark>不會試圖先同步</mark>,而是直接導出 `uv.lock` 的 lockfile 資訊。
+ #### `--locked`
> 斷言 `uv.lock` 在導出過程中,不會被更改。\
> (即斷言 <mark>`pyproject.toml` 和 `uv.lock` 一致</mark>)
### `cache`:快取
|🚨 <span class="caution">CAUTION</span>|
|:---|
|uv 為避免重複下載,採取激進快取策略,若太久沒清會很胖,要定期清|
+ #### `dir`
> 快取目錄 (通常是 `%LOCALAPPDATA%/uv/cache`)
+ 補充
```py
cache
├── archive-v0/ # 套件 hardlink (與虛擬環境 hardlink 指向同塊存放套件的空間)
├── interpreter-v4/ # ...
├── sdists-v9/ # ...
├── simple-v18/ # ...
├── wheels-v5/ # ...
├── .gitignore
├── .lock
└── CACHEDIR.TAG
```
+ #### `clean`
> 清除 - 清除所有快取
+ #### `prune`
> 修剪 - 清除舊版快取
|🔮 <span class="important">IMPORTANT</span>|
|:---|
|「修剪」中[舊版快取](https://github.com/astral-sh/uv/issues/10153#issuecomment-2564360859)的意思是,<mark>uv 因實作調整,而遺留下來的舊版目錄結構</mark>。 |
|比如在快取目錄中,有類似 `wheels-v5` 這樣的快取,它的上一版可能就是 `wheel-v4`,當 uv 版本更新時,這些舊版快取就會變成孤兒。 |
|🚨 <span class="caution">CAUTION</span>|
|:---|
| 所以 `uv cache prune` 和 `pnpm store prune` 實作上並不一樣。|
| pnpm 可以知道 hardlink 的 link count,然後去自動清理;uv 並沒有選擇這麼做。|
| 根據 [uv 的 contributer 所述](https://github.com/astral-sh/uv/issues/16008#issuecomment-3333296869),他們針對快取修剪這塊還在討論中,她認為 pnpm 的未用及刪不是好主意,她更傾向 LRU 的作法 (下載熱點保留、被冷落的修剪掉) |
### `build` :構建套件
### `publish`:發布套件
1. 發布到不同套件源 (比如 testpypi)
> 下指令時要指定套件源 `--index testpypi` (要先在 [`pyproject.toml` 設定](#--index:指定套件源)
2. 會問你 username 和 password (但現在不是改用 API token 登入?)
> 因此 username 要輸入 `__token__`,password 再輸入 API token 即可
### `pip`:相容 pip 介面
...
### `venv`:創建虛擬環境
> 預設目錄名 `.venv`
+ #### `--python`
> 指定虛擬環境 Python 版本
```
uv venv --python 3.11.4
```
### `tool`:工具
> 工具是一種 CLI 執行檔。\
> 會被安裝在獨立環境 (非專案內),以避免受不相關的依賴套件影響。
+ #### `run`
> 暫時下載工具 (可簡寫 `uvx`)
```
uv tool run ruff check
```
|📘 <span class="note">NOTE</span>|
|:---|
|CLI 執行檔會被放在 `~/.local/bin` (全域可見)|
+ #### `install` / `uninstall`
> 安裝 / 移除
+ 一般安裝
```
uv tool install ruff
```
+ 指定 Python 版本安裝
> 在工具未支持新版本 Python 時特別好用
```
uv tool install ruff --python 3.10
```
+ #### `dir`
> 工具被安裝在哪個目錄\
> (通常是 `%AppData%/Roaming/uv/tools`)
```
uv tool dir
```
### `lock`:生成 lockfile
...
### `auth`:套件上傳、下載需授權
+ 請參考:[PyPI Server](https://hackmd.io/@RogelioKG/pypi-server)
### `generate-shell-completion`:指令自動補全
+ 將 uv 的自動補全腳本,注入到初始化腳本內
```
uv generate-shell-completion powershell >> $PROFILE
```
|📘 <span class="note">NOTE</span>|
|:---|
|開啟 shell 時,會先執行一遍初始化腳本,註冊設定<br>PowerShell:放在 `$PROFILE`;Bash:放在 `~/.bashrc`|
## Options
### `--group` / `--no-group`:optional 依賴套件組 (開發者)
> 詳見 [dependency-groups](#dependency-groups:optional-依賴套件組-開發者)
+ 開發階段時,將 ruff 安裝到 dev 依賴套件組
```
uv add ruff --group dev
```
+ 開發階段時,安裝整個 dev 依賴套件組
```
uv sync --group dev
```
### `--index`:額外套件源
> 給定 url。此選項可重複多次,指定多個額外 index。
### `--default-index`:預設套件源
> 給定 url。指定優先度最高的 index。
### `--index-strategy`:多套件源選定策略
| 策略 | `first-index` | `unsafe-first-match` | `unsafe-best-match` |
| --- | --- | --- | --- |
| **行為** | 依 index 優先序解析,選擇優先序高的解析成功 index (預設) | 對每個依賴套件,依 index 優先序尋找版本,前一個 index 都沒合適版本,才換下一個 index | 對每個依賴套件,無視優先序從所有 index 找,若有多個則取優先序高的 index |
| **說明** | ✅ 依賴套件皆來自同 index | ☢️ 依賴套件可來自不同 index | ☢️ 依賴套件可來自不同 index |
| ☢️ <span class="warn">WARNING</span>|
| :------------------------------------- |
| <mark>混用套件源</mark>之所以被定調為 <mark>unsafe</mark>:<br>1. 通常不是 developer 在開發階段預期的情況,可能會出現不兼容的情況<br>2. 同名冒充套件的供應鏈攻擊 |
## Project Metadata
> 詳見 [uv - project metadata](https://docs.astral.sh/uv/reference/settings/#project-metadata)
### `project.optional-dependencies`:optional 依賴套件組 (使用者)
> PEP 621 規範。\
> 使用者可決定是否安裝的 optional 依賴:`uv add llm-crawler[proxy]`
|📗 <span class="tip">TIP</span>|
|:---|
|使用者若使用 wheel 安裝,也是可以的。<br>例如:`uv add ./llm_crawler-0.4.1-py3-none-any.whl[proxy]`|
```toml
[project]
name = "llm-crawler"
version = "0.1.0"
dependencies = [
"selenium",
"webdriver-manager",
]
[project.optional-dependencies]
proxy = [
"swiftshadow>=0.3.0",
]
```
### `tool.uv.dependency-groups`:optional 依賴套件組 (開發者)
> uv 擴充。\
> 開發者可決定是否安裝的 optional 依賴:`uv add --group lint`
```toml
[dependency-groups]
dev = [
"pytest"
]
lint = [
"ruff"
]
```
### `tool.uv.default-groups`:預設安裝 optional 依賴套件組 (開發者)
> uv 擴充。
```toml
[tool.uv]
default-groups = ["lint"]
```
### `tool.uv.index`:額外套件源
> uv 擴充。
```toml
[[tool.uv.index]]
name = "pytorch-cu124"
url = "https://download.pytorch.org/whl/cu124"
[[tool.uv.index]]
name = "pypi"
url = "https://pypi.org/simple/"
publish-url = "https://upload.pypi.org/legacy/"
[[tool.uv.index]]
name = "testpypi"
url = "https://test.pypi.org/simple/"
publish-url = "https://test.pypi.org/legacy/"
```
### `tool.uv.sources`:此套件需從【指定套件源】下載
> uv 擴充。
>
> `explicit = true`
> + 代表僅此套件的 wheel 檔從【指定套件源】抓取
> + 其餘依賴套件的 wheel 檔從【預設套件源】抓取
```toml
[tool.uv.sources]
torch = [
{ index = "pytorch-cu124"},
]
[[tool.uv.index]]
name = "pytorch-cu124"
url = "https://download.pytorch.org/whl/cu124"
explicit = true
```
## Project Structures
### namespace package
+ 有一種很特別的套件
+ install 時:`uv add google-auth google-cloud-storage`
+ import 時:`from google.auth import ...` `from google.cloud import ...`
+ 嗯?我剛剛裝的是 `google-auth` 和 `google-cloud-storage` 對吧?怎麼都變 `google` 了?
+ namespace package 的魅力
+ 使用者所見目錄
```py
site-packages/
│
└── google/ # 命名空間
├── auth/ # 子套件
│ ├── __init__.py
│ └── ...
├── outh2/ # 子套件
│ ├── __init__.py
│ └── ...
└── cloud/ # 子套件
├── __init__.py
└── ...
```
+ 實際開發目錄
```py
google-auth/ # 套件
│
└── google/
└── auth/ # 子套件
│ ├── __init__.py
│ └── ...
└── outh2/
├── __init__.py
└── ...
google-cloud-storage/ # 套件
│
└── google/
└── cloud/ # 子套件
├── __init__.py
└── ...
```
+ 優勢
+ 套件變成類似插件 (addons) 一樣
+ 根據需求下載需要的插件,每個插件裡包含不同功能的子套件
+ 插件本身也能依賴其他插件,這樣就能包成一個功能更強大的插件
+ 對於 developer 而言,每個插件可分配一個團隊開發
+ 對於 user 而言,所有插件仍歸屬同一個 namespace (統一品牌體驗)
+ 配置
+ `pyproject.toml`
```toml
[project]
...
[build-system]
requires = ["uv_build>=0.9.3,<0.10.0"]
build-backend = "uv_build"
[tool.uv.build-backend]
module-name = "google" # 命名空間
module-root = "" # 根目錄 (預設是 "src")
namespace = true # 使用 namespace package
```
+ 實際開發目錄
```py
google-auth/ # 套件
│
└── google/
└── auth/ # 子套件
│ ├── __init__.py
│ └── ...
└── outh2/
├── __init__.py
└── ...
```
### workspace
+ 參考
+ [**Using workspaces - uv**](https://docs.astral.sh/uv/concepts/projects/workspaces)
+ [**是 Ray 不是 Array - Monorepo**](https://israynotarray.com/other/20240413/3177435894/)
+ 簡單來說就是 <mark>monorepo</mark>
+ <mark>每個小專案 (package) 都有自己的設定</mark>(`pyproject.toml`)
+ 但由<mark>頂層專案 (workspace) 統一管理所有依賴</mark>(`uv.lock`)
+ 優勢
+ 每個小專案都可以作為套件發布 (不像 monolith 是單純的模組)
+ CI / CD 根據依賴 DAG 進行部分測試 (不像 monolith 改一行就要全部重測)
+ 專案目錄
```py
albatross # 頂層專案
├── packages
│ ├── bird-feeder # 小專案 1
│ │ ├── pyproject.toml
│ │ └── src
│ │ └── bird_feeder
│ │ ├── __init__.py
│ │ └── foo.py
│ └── seeds # 小專案 2
│ ├── pyproject.toml
│ └── src
│ └── seeds
│ ├── __init__.py
│ └── bar.py
├── pyproject.toml
├── README.md
├── uv.lock
└── src
└── albatross
└── main.py
```
+ `pyproject.toml`
```toml
[project]
name = "albatross"
version = "0.1.0"
requires-python = ">=3.13"
dependencies = [
"bird-feeder",
"seeds",
]
[tool.uv.sources]
bird-feeder = { workspace = true }
seeds = { workspace = true }
[tool.uv.workspace]
members = ["packages/*"]
```
+ `bird-feeder/pyproject.toml`
```toml
[project]
name = "bird-feeder"
version = "0.1.0"
description = "Add your description here"
requires-python = ">=3.13"
dependencies = ["httpx", "seeds"]
[build-system]
requires = ["uv_build>=0.9.5,<0.10.0"]
build-backend = "uv_build"
```
## Packaging
### multi-version
> 利用 `uv run --python <version>` 的特性,\
> 完全可以寫個小腳本,達成套件的 Python 多版本測試,\
> 而不需要再仰賴 [tox](https://tox.wiki/en/4.32.0/) 之類的工具。
```ps
# PowerShell
$ErrorActionPreference = "Stop"
$pyVersions = @("3.13", "3.12", "3.11", "3.10", "3.9", "3.8")
foreach ($v in $pyVersions) {
Write-Host ">>> Testing with Python $v" -ForegroundColor Cyan
# 這裡可執行 pytest 之類的單元測試
uv run --python $v -m src.llm_crawler.examples
if ($LASTEXITCODE -eq 0) {
Write-Host "✅ Python $v tests passed`n" -ForegroundColor Green
}
else {
Write-Host "❌ Python $v tests failed`n" -ForegroundColor Red
exit 1
}
Start-Sleep -Seconds 1
}
Write-Host "🎉 All versions tested successfully!" -ForegroundColor Green
```
## Run with
### Jupyter
1. 下載 `ipykernel` 套件
```
uv add ipykernel --group dev
```
2. 使用 VSCode 的話,Select Kernel 選擇虛擬環境的 Python Interpreter

3. 使用 Browser IDE 的話
```
uv run --with jupyter jupyter lab
```
## Others
+ Formatter 功能:[ruff](https://hackmd.io/@RogelioKG/ruff)
+ Precommit 功能:[pre-commit](https://hackmd.io/@RogelioKG/pre-commit)