この記事の対象読者
- 「uv」という名前を見かけて「pipと何が違うの?」と思った方
- pipとvenvで開発していて、requirements.txtの「自分の環境では動くのに」に疲れた方
- CIやDockerでのインストール時間を縮めたい方
この記事で得られること
- uvが「何者」なのか判断できる: pip・venv・pyenv・pip-toolsとの関係を、図書館のたとえで整理する
- 今日から乗り換えられる: pip互換モードのコマンド対応表と、requirements.txtからの移行手順
- 自分のケースの設定が決まる: 個人開発・チーム・CI・Dockerの設定ファイルを丸ごと載せる
この記事で扱わないこと
- ライブラリのビルドとPyPIへの公開(
uv build/uv publish。別記事で扱う) - 複数パッケージを1リポジトリで管理するワークスペース機能
- condaからの移行(バイナリ依存の扱いが別の話になるため)
1. uvとの出会い
「uv? UVカット? 紫外線でPythonを速くするのか?」
最初に名前を見たとき、正直そう思った。pipは知っている。venvも毎日使う。でも「uv」って何の略だ。
調べてみると、uvはpipの代替ではなく、pip・venv・pyenv・pip-tools・pipxの5つをまとめて置き換える1つのコマンドだった。
例えるなら、従来のPython環境は「各自が本を自費で買って自室に積む」やり方だ。プロジェクトごとに同じ本(パッケージ)を何冊も買い、買い物リスト(requirements.txt)には書名しか書いていない。uvは「図書館」を作る。書庫に本を1冊だけ置き、各閲覧席には貸出カードを置き、貸出記録には版まで正確に書く。以降、この図書館のたとえで通す。
自分がこれを調べたのは、以前書いたvLLMの記事で「uvでのインストールが公式推奨で速い」と書いたとき、なぜ速いのかを一言も説明できなかったからだ。手元はXeon 2.10GHzの1コア、メモリ3.9GBという貧弱なコンテナで、pipのインストール待ちが本当に長い。だからこそ差が数字で見えた。
読者に持ち帰ってほしいのは一点だけ。「速い」はおまけで、本命は「貸出記録(ロックファイル)」だということ。
以下では、uvがなぜ生まれ、何を解決し、自分のプロジェクトにどう入れるかを順に追う。
ここまでで、uvが「5つの道具を1つにしたもの」だとイメージできたと思う。次は、この記事で使う用語を整理しておこう。
2. 前提知識の確認
このセクションで分かること:本文で使う5つの用語の意味。
2.1 仮想環境(venv)とは
プロジェクトごとに独立したパッケージ置き場を作る仕組み。AプロジェクトのnumpyとBプロジェクトのnumpyが衝突しないようにする。図書館で言えば「閲覧席」だ。席ごとに手元に置く本が違う。
2.2 依存解決(Resolve)とは
「fastapiを入れたい」と言うと、fastapiが必要とするpydanticやstarletteも必要になる。全パッケージの要求を矛盾なく満たす版の組み合わせを探す作業が依存解決だ。図書館の「司書」が、リクエストの本と関連書の版を揃える仕事に当たる。
2.3 ロックファイルとは
依存解決の結果を、全パッケージの正確な版とハッシュ付きで記録したファイル。requirements.txtが「書名の買い物リスト」なら、ロックファイルは「いつ・何版を・どの印刷で借りたか」の貸出記録だ。同じ記録を使えば誰の環境でも同じ本が揃う。
2.4 ホイール(wheel)とは
ビルド済みのパッケージ配布形式(拡張子 .whl)。ダウンロードして展開するだけで使える。図書館に届く「製本済みの本」に相当する。
2.5 ハードリンクとは
1つのファイル実体に、複数の名前(パス)を付けるOSの機能。コピーと違い、ディスク上の実体は1つのまま。図書館の「貸出カード」だ。本は書庫に1冊、カードは席ごとに何枚でも。
これらの用語が押さえられたら、uvが生まれた背景を見ていこう。
3. uvが生まれた背景
このセクションで分かること:誰が、何を解決するためにuvを作ったか。
3.1 ruffの会社が作った、Rust製のツール
uvは、Pythonリンター「ruff」を開発したAstral社が作った。2024年2月に「pip・pip-toolsの代替」として公開され、同年8月にプロジェクト管理・Python本体のインストール・ツール実行までを統合した。本体はRustで書かれている。
出典: uv: Python packaging in Rust(Astral Blog, 2024-02) / uv: Unified Python packaging(Astral Blog, 2024-08)
3.2 Python環境管理が抱えていた4つの問題
| 問題 | 説明 | 図書館のたとえ |
|---|---|---|
| 道具が多い | pip・venv・pyenv・pip-tools・pipxを使い分ける必要がある | 購入係・席の予約係・閲覧室の建設係が全員別の窓口 |
| 遅い | pipは1パッケージずつ直列に処理し、毎回コピーする | 本を1冊ずつ買いに行き、席ごとに新品を買う |
| 再現できない | requirements.txtには版もハッシュもない | 買い物リストに「numpy」とだけ書いてある |
| Python本体は別 | 3.11が要るプロジェクトのためにpyenvを別途入れる | 閲覧室そのものは自分で建てろと言われる |
3.3 「Cargo」というヒント
Astral社は、Rustのパッケージ管理ツールCargoをお手本にした。Cargoは1つのコマンドで依存の追加・ロック・ビルド・実行を担い、ロックファイルをコミットする文化が最初からある。
| Cargo(Rust) | uv(Python) |
|---|---|
cargo add で依存を追加 |
uv add で依存を追加 |
Cargo.lock をコミット |
uv.lock をコミット |
cargo run でビルドと実行 |
uv run で同期と実行 |
| ツールチェーン(rustup)も管理 | Python本体(uv python)も管理 |
背景がわかったところで、この設計が「道具を減らす」以外に何を変えたのかが気になる。次はuvの仕組みを、数字と一緒に見ていこう。
4. 基本概念と仕組み
このセクションで分かること:uvが速くて再現性が高い理由と、pipとのコマンド対応。
4.1 グローバルキャッシュとハードリンク
uvの核心はここだ。ダウンロードしたホイールを1箇所(書庫)に展開し、各仮想環境(席)にはハードリンク(貸出カード)を置く。
【pip + venv】
プロジェクトA/.venv/ [numpy 実体 30MB] [pandas 実体 40MB] ...
プロジェクトB/.venv/ [numpy 実体 30MB] [pandas 実体 40MB] ... ← 同じ本を買い直す
プロジェクトC/.venv/ [numpy 実体 30MB] [pandas 実体 40MB] ...
【uv】
~/.cache/uv/ [numpy 実体 30MB] [pandas 実体 40MB] ← 書庫に1冊
プロジェクトA/.venv/ [→カード] [→カード]
プロジェクトB/.venv/ [→カード] [→カード] ← 実体は増えない
プロジェクトC/.venv/ [→カード] [→カード]
この図が言っているのは、プロジェクトが増えてもディスクとインストール時間がほぼ増えない、ということだ。同じrequirementsを3プロジェクトに入れて測った。
| 方式 | 3プロジェクト合計 | 備考 |
|---|---|---|
| pip + venv | 613 MB | 3席に3冊ずつ |
| uv | 156 MB | 書庫の実体+カード3枚分 |
表1:同一requirements(8パッケージ)を3プロジェクトに入れた合計ディスク使用量(実測。環境:Xeon 2.10GHz 1コア/メモリ3.9GB/Python 3.12.3/pip 24.0/uv 0.11.7。生成コードは直下)
# 同じ requirements を3プロジェクトに入れたときのディスク使用量を比較する
# 実行方法: bash disk.sh 前提: requirements.txt が同じディレクトリにあること
for i in 1 2 3; do
python3 -m venv pipproj$i/.venv && pipproj$i/.venv/bin/pip install -q -r requirements.txt
mkdir -p uvproj$i && (cd uvproj$i && uv venv -q && uv pip install -q -r ../requirements.txt)
done
echo "pip x3:"; du -shc pipproj1 pipproj2 pipproj3 | tail -1
echo "uv x3 (キャッシュ込み):"; du -shc uvproj1 uvproj2 uvproj3 "$(uv cache dir)" | tail -1
4.2 インストール時間
| 方式 | キャッシュなし | キャッシュあり |
|---|---|---|
| pip + venv | 17.2 秒 | 14.8 秒 |
| uv | 1.3 秒 | 0.2 秒 |
表2:8パッケージ(requests, fastapi, uvicorn, pandas, numpy, pydantic, httpx, sqlalchemy)を新規仮想環境に入れる時間(実測。環境は表1と同じ。ネットワークはキャッシュプロキシ経由のためダウンロード時間はほぼ含まれず、差は解決とインストール処理の時間。生成コードは直下)
#!/usr/bin/env bash
# pip と uv のインストール時間を、同じ requirements で比較する
# 実行方法: bash bench.sh 前提: python3 / pip / uv がPATHにあること
set -e
measure() { # $1=ラベル $2=コマンド
rm -rf .venv
local start=$(date +%s.%N)
eval "$2" > /dev/null 2>&1
local end=$(date +%s.%N)
printf "%-22s %6.1f 秒\n" "$1" "$(echo "$end - $start" | bc)"
}
pip cache purge > /dev/null 2>&1 || true
uv cache clean > /dev/null 2>&1 || true
measure "pip (キャッシュなし)" "python3 -m venv .venv && .venv/bin/pip install -q -r requirements.txt"
measure "pip (キャッシュあり)" "python3 -m venv .venv && .venv/bin/pip install -q -r requirements.txt"
measure "uv (キャッシュなし)" "uv venv -q && uv pip install -q -r requirements.txt"
measure "uv (キャッシュあり)" "uv venv -q && uv pip install -q -r requirements.txt"
pipが「キャッシュあり」でも14.8秒かかるのが意外だった。内訳を分けると、python -m venv だけで2.7秒(pip自体を席に複製している)、残りがファイルのコピーとバイトコード(.pyc)の生成だ。uvは仮想環境の作成が0.0秒、インストールはカードを置くだけなので0.2秒で終わる。
4.3 動作フロー:resolve → lock → sync
uvのプロジェクトモードは、3段階で動く。
-
resolve:
pyproject.tomlの要求から、司書(リゾルバ)が矛盾のない版の組み合わせを決める -
lock: 結果を
uv.lockに、全パッケージの版とハッシュ付きで書く(貸出記録) -
sync:
uv.lockどおりに.venvを作る。余分なパッケージは消し、足りないものは書庫からカードを置く
uv add や uv run は、この3段階を必要な分だけ自動で回す。手で uv lock や uv sync を打つのは、CIのように「記録どおりに揃えたい」場面だけだ。
4.4 コマンド対応表
| やりたいこと | 従来 | uv(pip互換) | uv(プロジェクトモード) |
|---|---|---|---|
| 仮想環境を作る | python -m venv .venv |
uv venv |
uv init(自動) |
| パッケージを入れる | pip install X |
uv pip install X |
uv add X |
| 開発用だけ入れる | 手で分ける | — | uv add --group dev X |
| 版を固定する | pip freeze > requirements.txt |
uv pip compile |
uv lock(自動) |
| 固定どおりに揃える | pip install -r requirements.txt |
uv pip sync |
uv sync --locked |
| Python本体を入れる | pyenv | — | uv python install 3.12 |
| CLIツールを使う | pipx | — | uvx ruff check . |
pip互換モードは「コマンドの頭に uv を付けるだけ」で既存の手順が動く。プロジェクトモードは、そこから pyproject.toml と uv.lock に移行した先の姿だ。
基本概念が理解できたところで、実際にuvを入れて動かしてみよう。
5. 実践:実際に使ってみよう
このセクションで分かること:インストールから設定ファイル、エラー対処、Dockerまでの一式。
5.1 環境構築
# macOS / Linux(公式インストーラ。~/.local/bin に置かれる)
curl -LsSf https://astral.sh/uv/install.sh | sh
# Homebrew
brew install uv
# Windows(PowerShell)
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
# 既にpipがある環境なら、これでも入る
pip install uv
uv --version # uv 0.x.y と出れば成功
注意: 公式インストーラは ~/.local/bin に置く。uv: command not found が出たらPATHを確認する。
5.2 環境別の設定ファイル
以下の3種類を用意した。用途に応じて選んでほしい。
個人開発用(pyproject.toml)
# pyproject.toml - 個人開発。uv init で生成されたものに依存を足しただけ
[project]
name = "myapp"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
"fastapi>=0.115",
"httpx>=0.28",
]
[dependency-groups]
dev = [
"pytest>=8.0",
"ruff>=0.8",
]
チーム共有用(pyproject.toml)
# pyproject.toml - チーム共有。uvの版とPythonの版を揃え、lockのずれをCIで落とす
[project]
name = "myapp"
version = "0.1.0"
requires-python = ">=3.12,<3.14" # 上限も切る。誰かの3.14で解決結果が変わるのを防ぐ
dependencies = [
"fastapi>=0.115",
"httpx>=0.28",
]
[dependency-groups]
dev = ["pytest>=8.0", "ruff>=0.8"]
docs = ["mkdocs>=1.6"] # 用途ごとにグループを分ける
[tool.uv]
required-version = ">=0.5" # 古いuvで lock を書き換えられないようにする
CI用(.github/workflows/test.yml)
# .github/workflows/test.yml - GitHub Actions。lockどおりに揃えてテストする
name: test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v6
with:
enable-cache: true # 書庫(キャッシュ)をジョブ間で持ち越す
- run: uv python install # .python-version の版を入れる
- run: uv sync --locked # lockがpyprojectとずれていたらここで失敗する
- run: uv run pytest
5.3 基本的な使い方
方法1: pip互換モード(既存プロジェクトにそのまま)
uv venv # .venv を作る
uv pip install -r requirements.txt # 既存の requirements.txt がそのまま使える
uv pip compile requirements.in -o requirements.txt # pip-tools の代替
方法2: プロジェクトモード(新規プロジェクト)
uv init myapp && cd myapp # pyproject.toml / .python-version / main.py を生成
uv add fastapi httpx # 依存を追加。lockと.venvが自動で更新される
uv add --group dev pytest ruff # 開発用グループ
uv run pytest # .venv を有効化せずに、その中で実行
方法3: ツール実行(pipxの代替)
uvx ruff check . # 一時環境にruffを入れて実行。プロジェクトを汚さない
uv tool install ruff # 常用するなら恒久インストール
5.4 実行結果
方法2を手元で実行したときの出力と、生成されたファイルは次のとおり。
$ uv init demo && cd demo && uv add fastapi httpx && uv add --group dev pytest ruff
Resolved 22 packages in 1ms
Installed 20 packages in 5ms
$ cat pyproject.toml
[project]
name = "demo"
version = "0.1.0"
requires-python = ">=3.13"
dependencies = [
"fastapi>=0.141.1",
"httpx>=0.28.1",
]
[dependency-groups]
dev = [
"pytest>=9.1.1",
"ruff>=0.16.6",
]
$ ls
README.md main.py pyproject.toml uv.lock
uv add が版の下限を自動で書き、uv.lock に22パッケージの正確な版を記録した。requirements.txtを手で書いていた作業が、ここで消える。
5.5 よくあるエラーと対処法
| エラー・症状 | 原因 | 対処 |
|---|---|---|
uv: command not found |
~/.local/bin がPATHにない |
export PATH="$HOME/.local/bin:$PATH" をシェル設定に追加 |
No solution found when resolving dependencies |
版の要求が矛盾している | メッセージ末尾の「どの2つが衝突したか」を読み、片方の下限を緩める |
warning: Failed to hardlink files ... falling back to copy |
キャッシュと .venv が別ファイルシステム(Dockerのvolume等) |
UV_LINK_MODE=copy を設定して警告を消す。速度は落ちない |
No interpreter found for Python 3.x |
要求されたPython本体がない |
uv python install 3.x。uvが本体をダウンロードする |
uv sync --locked がCIで失敗する |
pyproject.toml を変えて uv.lock を更新していない |
ローカルで uv lock してコミット |
Failed to build 'xxx' |
ホイールがなくソースからのビルドに失敗 |
uv pip install xxx --only-binary :all: で対応ホイールの有無を確認。なければビルド依存(gcc等)を入れる |
| 同僚と入る版が違う |
uv sync を --locked なしで実行し、lockが書き換わった |
CIと本番は必ず --locked。手元で uv lock --check
|
5.6 環境診断スクリプト
問題が出たら、まずこれを走らせる。uvが未インストールでも動き、原因を表示する。
#!/usr/bin/env python3
"""uv環境診断スクリプト
実行方法: python3 check_uv_env.py (プロジェクトのルートで実行する)
前提: Python 3.9以上。uvが未インストールでも動き、原因を表示する
"""
import shutil
import subprocess
import sys
from pathlib import Path
def run(cmd: list[str]) -> str:
"""コマンドを実行して標準出力を返す。失敗時は空文字"""
try:
return subprocess.run(cmd, capture_output=True, text=True, timeout=30).stdout.strip()
except (OSError, subprocess.TimeoutExpired):
return ""
def section(title: str) -> None:
print(f"\n== {title} ==")
def check_uv_binary() -> list[str]:
section("1. uv本体")
path = shutil.which("uv")
if not path:
return ["CRITICAL: uvが見つからない。~/.local/bin がPATHに入っているか確認"]
print(f" 場所: {path}\n {run(['uv', '--version'])}")
return []
def check_python() -> list[str]:
section("2. uvが認識しているPython")
listing = run(["uv", "python", "list", "--only-installed"])
print(" " + listing.replace("\n", "\n ") if listing else " なし")
return [] if listing else ["WARNING: Pythonが見つからない。uv python install 3.12 を実行"]
def check_cache() -> list[str]:
section("3. キャッシュとリンク方式")
cache = Path(run(["uv", "cache", "dir"]) or "")
if not cache.exists():
print(" キャッシュ未作成(初回インストール時に作られる)")
return []
size = sum(f.stat().st_size for f in cache.rglob("*") if f.is_file()) / 1e6
same_fs = cache.stat().st_dev == Path.cwd().stat().st_dev
print(f" 場所: {cache} サイズ: {size:.0f} MB")
print(f" カレントと同一ファイルシステム: {'はい(hardlink可)' if same_fs else 'いいえ'}")
return [] if same_fs else ["WARNING: 別ファイルシステムのためコピーになる。UV_LINK_MODE=copy を明示"]
def check_project() -> list[str]:
section("4. プロジェクト状態")
for name in ["pyproject.toml", "uv.lock", ".python-version", ".venv"]:
print(f" {name:16} {'あり' if Path(name).exists() else 'なし'}")
if not Path("uv.lock").exists():
return []
ok = subprocess.run(["uv", "lock", "--check"], capture_output=True).returncode == 0
print(f" lockとpyprojectの整合: {'OK' if ok else 'ずれあり'}")
return [] if ok else ["WARNING: uv.lock が古い。uv lock で更新してからコミット"]
def main() -> int:
issues = check_uv_binary()
if not issues:
issues += check_python() + check_cache() + check_project()
section("診断結果")
for i in issues:
print(f" [{'!' if 'CRITICAL' in i else '?'}] {i}")
if not issues:
print(" [OK] uvの実行環境は正常")
return 1 if any("CRITICAL" in i for i in issues) else 0
if __name__ == "__main__":
sys.exit(main())
5.7 Docker設定
# Dockerfile - 公式イメージを使い、依存だけ先に入れてレイヤーキャッシュ(Dockerが段階ごとに保存するビルド結果)を効かせる
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim
ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --locked --no-install-project --no-dev
COPY . .
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --locked --no-dev
ENV PATH="/app/.venv/bin:$PATH"
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
# docker-compose.yml - 上のDockerfileで動かすAPIサーバー
services:
api:
build: .
ports:
- "127.0.0.1:8000:8000"
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request as u; u.urlopen('http://localhost:8000/health')"]
interval: 30s
timeout: 5s
retries: 3
restart: unless-stopped
UV_LINK_MODE=copy は、イメージ内ではキャッシュと .venv が別レイヤーになるための指定だ。--mount=type=cache でビルド間の書庫を持ち越す。
実装方法がわかったので、次は具体的なユースケースを見ていく。
6. ユースケース別ガイド
このセクションで分かること:3つの典型的な状況で、何を打てばよいか。
6.1 ユースケース1: requirements.txt からの移行(既存プロジェクト)
想定読者: requirements.txtで動いている既存プロジェクトを、壊さずにuvへ移したい方
なぜuvなのか: uv add -r が既存のリストをそのまま読む。requirements.txtは移行後も uv export で生成できるので、後戻りが可能。
推奨構成: プロジェクトモードへ移行し、requirements.txtは生成物として残す
# 既存プロジェクトのルートで実行する
uv init --bare # pyproject.toml だけ作る(main.py等は作らない)
uv add -r requirements.txt # 依存を pyproject.toml に取り込み、lock を生成
uv add --group dev -r requirements-dev.txt # 開発用があれば
uv sync --locked # lockどおりに .venv を作り直す
uv export --format requirements-txt --no-hashes -o requirements.txt # 旧ツール向けに生成
移行後の requirements.txt は「編集するもの」から「生成するもの」に変わる。編集は pyproject.toml、記録は uv.lock、この2つをコミットする。
6.2 ユースケース2: 使い捨てスクリプトに依存を持たせる
想定読者: 「httpxを使う10行のスクリプト」のためだけに仮想環境を作るのが面倒な方
なぜuvなのか: PEP 723のインラインメタデータをuvが読み、実行時に一時的な閲覧席(仮想環境)を用意して、終われば片付ける。手元で測ると、初回でも1.3秒で起動した。
推奨構成: スクリプト先頭に依存を書き、uv run で実行
# /// script
# requires-python = ">=3.12"
# dependencies = ["httpx"]
# ///
"""PyPIからuvの最新版を取得する
実行方法: uv run fetch.py 前提: uvがインストール済み。仮想環境は不要
"""
import httpx
print(httpx.get("https://pypi.org/pypi/uv/json").json()["info"]["version"])
$ uv run fetch.py
Installed 7 packages in 5ms
0.12.10
6.3 ユースケース3: PyTorchをGPU版で固定する(機械学習)
想定読者: pip install torch でCPU版が入ってしまい、CUDA版を入れ直した経験がある方
なぜuvなのか: pyproject.toml に「torchだけは別の書庫(インデックス=パッケージの配布元サーバー)から取り寄せる」と書ける。全員が同じCUDA版のホイールを引く。
推奨構成: 専用インデックスを explicit = true で登録し、torchだけをそこに紐付ける
# pyproject.toml - torch だけ PyTorch 公式の CUDA 12.4 インデックスから取る
[project]
name = "mlproj"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["torch>=2.5", "numpy>=2.0"]
[tool.uv.sources]
torch = [{ index = "pytorch-cu124", marker = "sys_platform == 'linux'" }]
[[tool.uv.index]]
name = "pytorch-cu124"
url = "https://download.pytorch.org/whl/cu124"
explicit = true # このインデックスは明示的に紐付けたパッケージにしか使わない
explicit = true がないと、numpyまでPyTorchのインデックスから探しに行く。macOSではmarkerに一致しないので、通常のPyPIからCPU版が入る。この書き方は公式ドキュメントのPyTorchガイドに基づく。手元のコンテナにはGPUがないため、CUDA版が実際に動くことの確認はLinux+CUDA環境をお持ちの方に委ねる。
ユースケースを把握できたところで、この先の学習パスを確認しよう。
7. 学習ロードマップ
初級者向け(まずはここから)
-
uv公式ドキュメント - Getting startedでインストールし、
uv initからuv runまでを通す - 既存プロジェクトで
uv pip install -r requirements.txtを試し、pipとの時間差を体感する - 本記事の
bench.shを自分の環境で回す
中級者向け(実践に進む)
- 6.1の手順で1プロジェクトを移行し、
uv.lockをコミットしてCIに--lockedを入れる - Dependency groups(PEP 735)を読み、dev / docs / test の分け方を決める
- uv公式 - Docker integrationで本番イメージのレイヤー構成を最適化する
上級者向け(さらに深く)
- uv公式 - Workspacesで複数パッケージを1リポジトリで管理する
-
python-build-standaloneを読み、
uv python installが書庫に何を落としているかを知る - uvのGitHubリポジトリでリゾルバ(PubGrub)の実装を追う
8. まとめ
この記事では、uvについて以下を解説した。
- uvはpipの代替ではなく、5つの道具を1つにしたもの: pip・venv・pyenv・pip-tools・pipxの仕事を1コマンドで担う
- 速さの正体は図書館方式: 書庫(グローバルキャッシュ)に1冊、席にはハードリンク。3プロジェクトで613MBが156MBに、インストール14.8秒が0.2秒になった
-
本命はロックファイル:
uv.lockをコミットし、CIと本番で--lockedを付ける。これで「自分の環境では動く」が消える
各ツールの立ち位置まとめ
| ツール | 主な用途 | ロックファイル | Python本体の管理 | 導入コスト |
|---|---|---|---|---|
| pip + venv | 標準。何もしなくても使える | なし | なし | 0分 |
| pip-tools | requirements.txt の版固定 | あり(requirements.txt) | なし | 10分 |
| Poetry | プロジェクト管理 | あり(poetry.lock) | なし | 30分 |
| uv | 上の全部 | あり(uv.lock) | あり | 5分 |
私の所感
uvを調べていて一番感動したのは、「速さ」の理由がアルゴリズムの魔法ではなく、同じ本を何冊も買わないという当たり前の設計だったことだ。ハードリンクはOSに何十年も前からある機能で、pipがそれを使わなかっただけだった。
一方で、uvが答えにならない場面もある。condaで管理しているCUDAやMKLのようなバイナリ依存は、uvの守備範囲外だ。既存のPoetryプロジェクトが安定して回っているなら、移行の手間に見合う差は「速さ」しかない。
自分の習慣として変わったのは、新しいスクリプトを書くとき、まず仮想環境を作らなくなったことだ。先頭に3行のメタデータを書いて uv run する。vLLMの記事で「速いから」と書いた一言に、ようやく理由を付けられた。
参考文献
- uv 公式ドキュメント — コマンドリファレンス、設定項目、各種インテグレーション
- astral-sh/uv(GitHub) — ソースコードとリリースノート
- uv: Python packaging in Rust(Astral Blog, 2024-02-15) — 初公開時の設計思想(邦題訳:uv、Rust製のPythonパッケージング)
- uv: Unified Python packaging(Astral Blog, 2024-08-20) — プロジェクト管理・Python管理・ツール実行の統合(邦題訳:uv、統合されたPythonパッケージング)
- PEP 723 – Inline script metadata — 6.2で使ったスクリプト先頭のメタデータ仕様(邦題訳:スクリプト内インラインメタデータ)
-
PEP 735 – Dependency Groups in pyproject.toml —
[dependency-groups]の仕様(邦題訳:pyproject.tomlにおける依存グループ) - astral-sh/uv-docker-example(GitHub) — 5.7のDockerfileの元になった公式サンプル
この記事が役に立ったら、いいね・ストックしていただけると励みになります。
他にもPython・ローカルLLM関連の記事を書いています: