はじめに
Pythonのパッケージ管理ツール「uv」を使っている人は増えてきたが、「速いpip」程度の認識で使っていないだろうか?
実は uv には明確な設計思想があり、それを理解せずに使うと本来の力を発揮できない。本記事では uv の設計思想を解説し、再現性のある環境構築のためのベストプラクティスを紹介する。
uv とは
uv は Astral 社が開発した、Rust製のPythonパッケージ・プロジェクト管理ツールだ。
従来のPython開発では複数のツールを組み合わせる必要があった:
-
pyenv: Pythonバージョン管理 -
venv/virtualenv: 仮想環境作成 -
pip: パッケージインストール -
pip-tools: 依存関係のロック -
build/twine: パッケージのビルド・公開
uv はこれらを単一のツールで置き換えることを目指している。しかも10〜100倍速い。
uv の設計思想: pyproject.toml を Single Source of Truth に
uv の最も重要な設計思想は、pyproject.toml をプロジェクトの唯一の真実の情報源(Single Source of Truth)にすることだ。
従来の問題点
従来のpipベースのワークフローでは、依存関係の情報が散らばりがちだった:
requirements.txt # 本番依存
requirements-dev.txt # 開発依存
requirements-test.txt # テスト依存
setup.py # パッケージメタデータ
setup.cfg # 追加設定
どれが正なのか? 更新漏れは? 同期は取れている? こうした問題が常につきまとう。
uv のアプローチ
uv では pyproject.toml に全ての情報を集約する:
[project]
name = "my-project"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
"httpx>=0.27.2",
"pydantic>=2.0.0",
]
[project.optional-dependencies]
excel = ["openpyxl>=3.1.0"]
[dependency-groups]
dev = ["pytest>=8.0.0", "ruff>=0.4.0"]
test = ["pytest-cov>=5.0.0"]
[tool.uv.sources]
# 開発時のみ使う特殊なソース指定
この1ファイルを見れば、プロジェクトの全ての依存関係がわかる。
uv pip install vs uv add: 2つのインターフェースの違い
ここが最も誤解されやすいポイントだ。
uv には2つの異なるインターフェースがある:
1. uv pip 系コマンド(pip互換レイヤー)
uv pip install httpx
uv pip install -r requirements.txt
uv pip freeze
これらは pip からの移行を容易にするための互換レイヤー として提供されている。
特徴:
- venv環境に直接パッケージをインストール
-
pyproject.tomlは変更しない -
uv.lockも更新しない - 一時的なテストや、pipとの互換性が必要な場面向け
2. uv add / uv sync 系コマンド(uvネイティブ)
uv add httpx
uv add -r requirements.txt # requirements.txtからインポート
uv sync
uv lock
これが uv 本来の使い方 だ。
特徴:
-
pyproject.tomlの[project.dependencies]を更新 -
uv.lockを自動生成・更新 -
tool.uv.sourcesにソース情報を記録 - 再現性のある環境構築が可能
比較表
| コマンド | pyproject.toml | uv.lock | 主な用途 |
|---|---|---|---|
uv add <package> |
更新 | 更新 | 正式な依存追加 |
uv remove <package> |
更新 | 更新 | 依存の削除 |
uv sync |
読み取り | 適用 | lockfileから環境を同期 |
uv lock |
読み取り | 更新 | lockfileの再生成 |
uv pip install |
変更なし | 変更なし | 一時的なテスト |
なぜ uv pip install requirements.txt が推奨されないのか
# これをやると...
uv pip install -r requirements.txt
# pyproject.toml には何も残らない
# チームメンバーが uv sync しても何もインストールされない
# 再現性ゼロ
正しいアプローチ:
# requirements.txt の内容を pyproject.toml にインポート
uv add -r requirements.txt
再現性ある環境構築のベストプラクティス
1. プロジェクトの初期化
# 新規プロジェクト
uv init my-project
cd my-project
# 既存プロジェクトに uv を導入
uv init --bare
2. 依存関係の追加
# 本番依存
uv add httpx pydantic
# 開発依存
uv add --dev pytest ruff mypy
# 特定のグループに追加
uv add --group test pytest-cov
# オプショナル依存(extras)
uv add --optional excel openpyxl
3. 環境の同期
# uv.lock に基づいて環境を同期(推奨)
uv sync
# 開発依存を含めて同期
uv sync --all-groups
# 特定のグループのみ
uv sync --group test
4. uv.lock のバージョン管理
uv.lock は必ず Git にコミットする。 これが再現性の鍵だ。
# .gitignore
.venv/
__pycache__/
# uv.lock は含めない!コミットする!
uv.lock があれば、どの環境でも同じバージョンの依存関係がインストールされる。
5. CI/CD での利用
# GitHub Actions の例
- name: Install uv
uses: astral-sh/setup-uv@v4
- name: Install dependencies
run: uv sync --frozen # lockfile を変更しない
- name: Run tests
run: uv run pytest
--frozen フラグにより、CI環境でlockfileが意図せず更新されることを防ぐ。
依存ソースの指定(tool.uv.sources)
uv の強力な機能の1つが tool.uv.sources だ。開発時のみ使う特殊なソースを指定できる。
Git リポジトリから
[project]
dependencies = ["httpx"]
[tool.uv.sources]
httpx = { git = "https://github.com/encode/httpx", tag = "0.27.0" }
ローカルパスから
[project]
dependencies = ["my-lib"]
[tool.uv.sources]
my-lib = { path = "../my-lib", editable = true }
特定のインデックスから
[project]
dependencies = ["torch"]
[tool.uv.sources]
torch = { index = "pytorch" }
[[tool.uv.index]]
name = "pytorch"
url = "https://download.pytorch.org/whl/cpu"
重要: tool.uv.sources は開発時のみ使用される。パッケージを公開する際は --no-sources でビルドすることが推奨される。
uv build --no-sources
よくある間違いと対処法
間違い1: uv pip install をメインで使う
# NG: pyproject.toml に反映されない
uv pip install pandas numpy
# OK: 依存関係として記録される
uv add pandas numpy
間違い2: uv.lock を .gitignore に入れる
# NG: 再現性が失われる
uv.lock
# OK: uv.lock はコミットする
# (.gitignore に含めない)
間違い3: uv sync せずに uv run する
# 実は uv run は自動で sync してくれる
uv run pytest # OK: 内部で sync が走る
# 明示的に sync したい場合
uv sync && uv run pytest
uv run は実行前に自動で環境を同期するので、常に pyproject.toml と uv.lock に基づいた正しい環境でコマンドが実行される。
間違い4: 仮想環境を手動で activate する
# 従来のやり方(uvでは不要)
source .venv/bin/activate
python script.py
# uv のやり方
uv run python script.py
uv run pytest
uv run を使えば、仮想環境の activate は不要。
まとめ
uv を「速いpip」として使うのはもったいない。その設計思想を理解して使おう:
-
pyproject.tomlを Single Source of Truth に: 全ての依存情報を1箇所に集約 -
uv addを使う:uv pip installではなくuv addで依存を追加 -
uv.lockをコミット: 再現性のある環境構築の鍵 -
uv sync/uv runを活用: 常に正しい環境で作業
これらを実践すれば、「自分の環境では動くのに...」という悲劇とはおさらばできる。