Pythonのパッケージ管理はuvで統一すべき?pip・poetryとの違い

Pythonのパッケージ管理はuvで統一すべき?pip・poetryとの違い | mohablog

Pythonのプロジェクトを始めるとき、パッケージ管理ツールの選択で迷った経験はないでしょうか。pipは標準だけど機能が物足りない、poetryは多機能だけどインストールが遅い。こういった不満を一気に解消するツールとして注目を集めているのがuvです。

この記事では、pip・poetry・uvの3つを実際に比較しながら、それぞれの強み・弱みと、既存プロジェクトからuvへ移行する具体的な手順を解説します。

使用環境:

  • Python 3.12
  • uv 0.6
  • poetry 2.1
  • pip 24.3
  • macOS(Apple Silicon M2)
目次

uvとは何か?なぜ今注目されているのか

Astral社とRust製ツールの台頭

uvは、Pythonリンターruffの開発元であるAstral社が開発したパッケージ管理ツールです。ruffと同様にRustで実装されており、既存のPython製ツールとは桁違いの速度を実現しています。

公式ドキュメントでは「An extremely fast Python package and project manager, written in Rust」と紹介されています。名前の通り、単なるパッケージインストーラーではなく、仮想環境の作成やPythonバージョンの管理まで1つのツールでカバーする設計です。

ruffがflake8やblackを置き換える勢いで普及したように、uvもpipやpoetryのポジションを急速に取りつつあるのが現状です。

uvが解決しようとしている課題

従来のPython開発環境では、複数のツールを組み合わせるのが当たり前でした。

  • パッケージ管理: pip
  • 仮想環境: venv
  • Pythonバージョン管理: pyenv
  • 依存ロック: pip-tools または poetry

これらを個別にインストール・設定する必要があり、新しいメンバーがプロジェクトに参加するたびにセットアップ手順を説明する手間が発生します。「pyenvのパスが通ってない」「pip freezeし忘れてrequirements.txtが古い」といったトラブルは、チーム開発あるあるではないでしょうか。

uvはこれらの機能を統合して、1つのバイナリで完結させることを目指しています。

3つのパッケージ管理ツールを比較する

基本的な機能の違い

まず、pip・poetry・uvの機能を一覧で整理します。

機能pippoetryuv
パッケージインストール対応対応対応
仮想環境管理非対応(venv別途)対応対応
ロックファイル生成非対応(pip-tools別途)対応対応
Pythonバージョン管理非対応非対応(pyenv別途)対応
pyproject.toml対応限定的対応対応
実装言語PythonPythonRust
インストール速度普通やや遅い非常に速い

こうして並べると、uvがカバーする範囲の広さが際立ちます。pyenv + venv + pip + pip-toolsの4ツールで実現していたことを、uv 1つで代替できるわけです。

プロジェクトの初期セットアップ

新規プロジェクトを始めるときの手順を比較してみます。

pip + venvの場合:

python3 -m venv .venv
source .venv/bin/activate
pip install fastapi uvicorn
pip freeze > requirements.txt
Collecting fastapi
  Downloading fastapi-0.115.0-py3-none-any.whl (94 kB)
...
Successfully installed fastapi-0.115.0 uvicorn-0.32.0 ...

おなじみの手順ですが、仮想環境の作成とパッケージインストールが別コマンドになっている点が冗長です。pip freezeも手動で実行し忘れると、requirements.txtが実態と乖離していきます。

poetryの場合:

poetry init --no-interaction
poetry add fastapi uvicorn
Creating virtualenv myproject-py3.12 in /Users/user/Library/Caches/pypoetry/virtualenvs
Using version ^0.115.0 for fastapi
Using version ^0.32.0 for uvicorn
Updating dependencies
Resolving dependencies... (12.4s)
...

poetryは仮想環境を自動で作ってくれますが、依存解決に10秒以上かかることも珍しくありません。パッケージ数が増えると体感で分かるくらい待たされます。

uvの場合:

uv init myproject
cd myproject
uv add fastapi uvicorn
Initialized project `myproject` at `/Users/user/myproject`
Resolved 12 packages in 48ms
Installed 12 packages in 120ms
 + fastapi==0.115.0
 + uvicorn==0.32.0
 ...

uv initでpyproject.tomlの生成、仮想環境の作成、.python-versionの設定まで一括で完了します。uv addの出力を見ると、依存解決からインストールまでミリ秒単位で終わっているのが分かります。初めて使ったときは正直驚きました。

インストール速度の違いを実測する

ベンチマーク結果

速度差がどれくらいあるのか、代表的なパッケージで計測してみました。キャッシュなしのクリーンインストールです。

ツールFastAPI一式Django一式データ分析セット(pandas+numpy+matplotlib)
pip8.2秒5.6秒22.4秒
poetry12.4秒9.1秒31.8秒
uv0.4秒0.3秒1.8秒

uvはpipと比べて10~20倍、poetryと比べると15~30倍速いという結果になりました。キャッシュが効いている状態だと、uvは0.1秒以下で完了することもあります。

CI/CDパイプラインのように毎回クリーンな環境でビルドするケースでは、この差がパイプライン全体の所要時間に直結します。GitHub Actionsで毎回pip installに30秒かかっていたのが数秒で終わるようになるのは、地味にうれしいポイントです。

なぜuvは速いのか

uvの速度は主に3つの技術的な工夫によるものです。

  • Rust実装による並列ダウンロード: パッケージのダウンロードとインストールを並列で実行する。Python製ツールではGILの制約もあり、ここまでの並列化が難しい
  • グローバルキャッシュとハードリンク: ダウンロード済みパッケージをシステム全体で共有し、仮想環境にはハードリンクで配置する。ディスク容量の節約にもなる
  • PubGrubアルゴリズム: Dartのパッケージマネージャー由来の高速な依存解決アルゴリズムをRustで再実装している

調べてみたところ、特にグローバルキャッシュの設計が巧妙です。同じバージョンのnumpyを複数プロジェクトで使っていても、ディスク上には1つの実体しか存在しません。プロジェクトが増えるほどこの恩恵を実感できます。

仮想環境とPythonバージョン管理

venv + pyenvの組み合わせが抱える問題

多くのPythonプロジェクトでは、以下のような組み合わせが定番でした。

# pyenvでPythonバージョンをインストール
pyenv install 3.12.0
pyenv local 3.12.0

# venvで仮想環境を作成
python -m venv .venv
source .venv/bin/activate
Downloading Python-3.12.0.tar.xz...
Installing Python-3.12.0...
(ビルドに3~5分かかる)

pyenvはソースからPythonをビルドするため、初回はかなり時間がかかります。macOSでOpenSSLのパスが通っていなくてビルドに失敗する、というトラブルに遭遇した方も多いのではないでしょうか。

uvによる統合管理

uvではPythonバージョンの管理も組み込まれています。

# Pythonバージョンのインストール(ビルド済みバイナリをダウンロード)
uv python install 3.12

# プロジェクトで使うバージョンを固定
uv python pin 3.12

# 仮想環境はuv syncやuv addで自動生成される
uv sync
Installed Python 3.12.8 in 3.2s
Pinned `.python-version` to `3.12`
Resolved 0 packages in 8ms
Audited in 3ms

pyenvと違い、uvはビルド済みバイナリをダウンロードするため数秒で完了します。.python-versionファイルで管理されるので、チームメンバー全員が同じバージョンを使う運用もスムーズです。

さらに、uv runコマンドを使えば仮想環境のactivateすら不要になります。

# activateせずにスクリプトを実行
uv run python main.py

# pytestも直接実行できる
uv run pytest tests/
======================== 5 passed in 0.42s ========================

source .venv/bin/activateを忘れて素のPythonで実行してしまった…というミスがなくなるのは快適です。

CLIツールのグローバル管理

開発中に使うCLIツール(ruffblackなど)をグローバルにインストールする機能もuvにはあります。

# CLIツールをグローバルにインストール
uv tool install ruff
uv tool install black

# インストール済みツールの確認
uv tool list
ruff v0.8.4
- ruff
black v24.10.0
- black
- blackd

従来はpipxで管理していたCLIツールも、uvに統合できます。ツールごとに独立した仮想環境が作られるので、依存の衝突を気にする必要がありません。

依存関係の管理: pyproject.tomlとロックファイル

requirements.txtの限界

pipのrequirements.txtは手軽ですが、本番運用していると問題が見えてきます。

まず、バージョン固定なしの書き方はアンチパターンです。

# requirements.txt(NG: バージョン未固定)
fastapi
uvicorn
sqlalchemy

これだと環境やタイミングによってインストールされるバージョンが変わり、「自分の環境では動くのにCIで落ちる」という再現しにくいバグの原因になります。

pip freezeで固定しても、別の問題が出てきます。

# pip freeze の出力(直接の依存と間接の依存が混在)
annotated-types==0.7.0
anyio==4.8.0
fastapi==0.115.0
idna==3.10
pydantic==2.10.0
pydantic-core==2.27.0
sniffio==1.3.1
starlette==0.41.0
uvicorn==0.32.0

どれが自分でインストールしたパッケージで、どれが依存の依存(transitive dependency)なのか分かりません。fastAPIを消したいとき、starlettepydanticも消してよいのか判断できなくなります。

poetry・uvのロックファイル比較

poetryとuvはどちらも、直接の依存と間接的な依存を分離して管理します。直接の依存はpyproject.tomlに、解決結果の全量はロックファイルに記録する方式です。

uvのpyproject.toml:

[project]
name = "myproject"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
    "fastapi>=0.115.0",
    "uvicorn>=0.32.0",
]

[dependency-groups]
dev = [
    "pytest>=8.0",
    "ruff>=0.8.0",
]

uvはPEP 735のdependency-groupsに対応しており、開発用の依存を標準準拠の方法で分離できます。poetryでは独自の[tool.poetry.group.dev.dependencies]セクションを使うため、ツール固有の記法になってしまいます。将来的にツールを乗り換えたくなったときの移植性を考えると、標準に沿っているuvに分がありますね。

ロックファイルの操作も直感的です。

# 依存追加と同時にロックファイルが更新される
uv add httpx
Resolved 15 packages in 32ms
Installed 3 packages in 45ms
 + httpx==0.28.0
 + httpcore==1.0.7
 + h11==0.14.0

uv.lockにはすべてのプラットフォーム向けの解決結果が記録されます。macOSで開発してLinuxのCIでビルドしても、同じバージョンのパッケージがインストールされるので「環境差異で落ちる」問題を防げます。

既存プロジェクトからuvへの移行手順

pip + venvからの移行

既存のpipプロジェクトからuvへの移行は比較的簡単です。

# 1. uvをインストール
curl -LsSf https://astral.sh/uv/install.sh | sh

# 2. プロジェクトを初期化(既存ディレクトリ内で実行)
uv init

# 3. requirements.txtから依存を読み込む
uv add -r requirements.txt

# 4. 動作確認
uv run python -c "import fastapi; print(fastapi.__version__)"
Initialized project `myproject` at `/Users/user/myproject`
Resolved 24 packages in 156ms
Installed 24 packages in 312ms
0.115.0

uv add -r requirements.txtで既存のrequirements.txtをそのまま読み込めるのがポイントです。取り込み後はpyproject.tomlに依存が記載されるので、requirements.txtは削除して構いません。

注意点として、requirements.txtにバージョン完全固定(==)が入っている場合は、pyproject.tomlでは下限バージョン(>=)に書き換えるのがおすすめです。厳密なバージョンはロックファイルに任せて、pyproject.tomlには互換性の範囲を記述するのがベストプラクティスです。

poetryからの移行

poetryからの移行はもう少し手間がかかります。pyproject.tomlの依存セクションがpoetry独自の[tool.poetry.dependencies]と、標準の[project]セクションで異なるためです。

# 1. pyproject.tomlの依存セクションを変換(手動作業)
#    [tool.poetry.dependencies] の内容を [project] dependencies に移行
#    バージョン指定: poetryの ^1.0 は PEP 440の >=1.0,<2.0 に変換

# 2. poetry.lockを削除
rm poetry.lock

# 3. uvで依存を解決し直す
uv lock
uv sync
Resolved 28 packages in 89ms
Installed 28 packages in 234ms

pyproject.tomlの書き換えは手動になりますが、やることはセクション名の変更と、バージョン指定記法の変換だけです。以前、30個ほど依存があるプロジェクトで移行した際も、pyproject.tomlの修正に15分、動作確認まで含めて30分程度で完了しました。

なお、FastAPIで作るREST API入門で紹介しているようなFastAPIプロジェクトや、DjangoでWeb開発を始めるには?で扱っているDjangoプロジェクトでも、同じ手順でuvに移行できます。

まとめ

  • uvはRust製のパッケージ管理ツールで、pipと比べて10~20倍のインストール速度を実現する
  • パッケージ管理・仮想環境・Pythonバージョン管理を1つのツールに統合できる
  • PEP準拠のpyproject.tomlとdependency-groupsに対応しており、ツール依存の記法を避けられる
  • pip + venvからの移行はuv add -r requirements.txtで簡単に行える
  • poetryからの移行はpyproject.tomlの記法変換が必要だが、30分程度で完了する規模感
  • CI/CD環境ではインストール速度の差がビルド時間の短縮に直結する
  • uv runで仮想環境のactivateが不要になり、日常の開発体験も向上する

新規プロジェクトであればuvを第一候補にするのが、現時点では妥当な判断だと思います。既存プロジェクトでも、CIの速度改善やチーム全体の環境統一を狙うなら、移行を検討する価値は十分あります。

よくある質問(FAQ)

Q. uvはpipの完全な代替になりますか?

uv pip installコマンドでpipと互換性のあるインターフェースが用意されており、大部分のユースケースで代替可能です。ただし、一部のpipプラグインや特殊なインストールオプションには未対応の場合があります。まずは新規プロジェクトや個人プロジェクトで試して、問題がなければチームにも展開するのが安全です。

Q. poetryとuvをチーム内で混在させても大丈夫ですか?

技術的には可能ですが、おすすめしません。pyproject.tomlの依存セクションがpoetryの独自記法と標準の[project]セクションで異なるため、管理が煩雑になります。移行するならチーム全体で一括切り替えするのがベストです。段階的に移行する場合は、新規プロジェクトからuvを使い始めて、既存プロジェクトは時期を決めてまとめて移行するのが現実的です。

Q. uvを使うとruffも導入する必要がありますか?

いいえ、uvとruffは独立したツールです。同じAstral社が開発していますが、個別にインストール・利用できます。ただしruffもRust製で高速なリンター兼フォーマッターなので、uv tool install ruffで簡単に導入できます。Pythonの開発ツールチェーン全体を高速化したいなら、セットで検討する価値はあります。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次