uv入門|Pythonの依存管理・仮想環境・lockfileを最短で整える

Pythonの開発を始めると、最初は「とりあえずpip install」で進められます。しかし、別のPCで動かない、同じリポジトリなのにメンバーごとに結果が違う、仮想環境を有効化し忘れる、依存関係がいつの間にか変わる、といった問題はすぐに現れます。小さなスクリプトでも、再現できる環境を最初に整える方が、後から直すよりずっと楽です。

そこで役立つのが uv です。uvはPythonのバージョン、プロジェクト、依存関係、仮想環境、ツール実行をまとめて扱えるツールです。本記事では、すべての機能を一度に覚えるのではなく、uv init、uv add、uv run、uv lock、uv syncという日常で使う流れに絞って解説します。

この記事の結論

  • 新規プロジェクトはuv initから始め、設定をpyproject.tomlへ集める
  • 依存関係はuv addで追加し、解決済みの正確な組み合わせはuv.lockで共有する
  • 日々の実行はuv runを基本にすると、仮想環境とlockfileの同期漏れを減らせる
  • uv.lockは原則Gitへ入れ、手編集せず、変更の意味をレビューする
  • プロジェクト外の一時実行、複数パッケージ、CIは同じ原則を保ったまま段階的に広げられる

uvで整えるPythonプロジェクトの全体像

本文図1:pyproject.tomlで意図を表し、uv.lockで解決結果を固定し、.venvで実行環境を揃える。

スポンサーリンク

uvは何をまとめるツールか

uvは、Python開発で分散しがちな作業を一つの操作体系にまとめます。Python本体の導入、仮想環境、パッケージ追加、lockfile、コマンド実行、公開済みツールの一時起動などを扱えます。

すべてをuvへ移す必要はありません。すでに安定しているPoetry、pip-tools、condaなどの環境を、理由なく置き換えるのはリスクになります。uvが特に効果を出しやすいのは、新しいPythonプロジェクト、複数人で再現性を保ちたいリポジトリ、CIとローカルの差を減らしたい場面です。

まず覚えたいのは、設定と結果を分ける考え方です。pyproject.tomlには「このプロジェクトは何を必要とするか」を書きます。uv.lockには「その条件を解いた結果、どのバージョンを使うか」を記録します。前者は意図、後者は再現可能な解決結果です。

スポンサーリンク

5つのファイルとコマンドの関係

要素 役割 原則
pyproject.toml プロジェクトの情報と依存条件 人が意図して編集・レビューする
uv.lock 正確に解決された依存関係 Gitへ入れ、手で編集しない
.python-version プロジェクトで使うPythonの目安 チームで揃える
.venv/ ローカルの実行環境 通常はGitへ入れない
uv run lockfileと環境を確認して実行 日常の標準入口にする

uv initで新しいプロジェクトを作ると、基本的なpyproject.toml、README、Pythonバージョンのピン、ソース用ディレクトリなどが用意されます。実際の生成物はテンプレートやオプションで変わりますが、重要なのは「環境を人の記憶ではなくファイルとして残す」ことです。

最小の始め方

空のディレクトリで次を実行します。

uv init sample-app
cd sample-app
uv run python -c "print('hello from uv')"

最初のuv runでは、必要に応じて仮想環境とlockfileが作られます。pythonを直接呼ぶ代わりにuv runを使うと、現在のプロジェクトの環境で実行されることが明確になります。シェルで毎回source .venv/bin/activateを打つ運用より、実行対象がリポジトリと結び付きやすいのが利点です。

プロジェクトを作ったら、まずpyproject.tomlを開きます。最低限の名前、バージョン、Python要件、依存関係が置かれます。アプリケーションなのかライブラリなのか、パッケージとして配布するのか、単発スクリプトなのかで、初期構成は変わります。公開しない小さなスクリプトなら、パッケージ化を省く選択もあります。

依存関係はuv addで追加する

HTTPリクエストにhttpxを使いたい場合は、次のように追加します。

uv add httpx
uv run python -c "import httpx; print(httpx.__version__)"

uv addは、依存関係をpyproject.tomlへ反映し、lockfileと環境も更新します。手元でpip install httpxだけを実行すると、後から見た人はなぜそのパッケージが必要なのか、どの条件で入ったのかを追いにくくなります。依存関係の変更をプロジェクト設定へ集めることで、PRの差分も読みやすくなります。

バージョンを厳密に固定するか、範囲で指定するかは用途で決めます。アプリケーションではデプロイ再現性を優先し、ライブラリでは利用者との互換性を考える必要があります。いずれの場合も、実際に使う正確な解決結果はlockfileへ残ります。pyproject.tomlの条件だけを見て「同じ環境になる」と考えないことが重要です。

uv.lockはなぜ必要か

ある依存関係を追加すると、その依存先がさらに多くのパッケージを必要とします。同じhttpxでも、証明書、HTTPコア、文字コード検出などを含む組み合わせが変わることがあります。条件だけでは、翌週に解決した環境が今日と同じになるとは限りません。

uv.lockは、解決された正確なパッケージ群とバージョン、取得元などを記録するファイルです。公式ドキュメントでは、uvのlockfileはクロスプラットフォームのlockfileとして説明されています。通常はGitへコミットし、開発者とCIが同じ解決結果を使えるようにします。

lockfileを手で直すのは避けます。競合が起きた場合も、意味の分からない行を削って合わせるより、どちらの依存条件を採用するか決めてから、uvに解決させます。差分が大きくなったときは、直接依存の変更、Python要件の変更、解決器の更新のどれが原因かを確認します。

uv runとuv syncの使い分け

日々の作業では、まずuv runを標準入口にするのがおすすめです。uv runは、実行前にlockfileと環境がプロジェクト設定に合っているかを確認します。スクリプトやテスト、フォーマッタを実行するときに、仮想環境を有効化しているかを気にする回数を減らせます。

uv run pytest
uv run ruff check .
uv run python src/sample_app/main.py

一方で、環境だけを先に整えたい場合や、IDEへ.venvを認識させてから作業したい場合はuv syncが向きます。uv syncで環境をlockfileへ合わせ、その後に通常のpythonやIDEの実行ボタンを使う運用もできます。ただし、チームで「誰かはuv run、誰かは古い.venvを直接使う」と混ざると差が生まれやすくなります。実行ルールをREADMEやCLAUDE.mdへ一行書いておくとよいでしょう。

Pythonバージョンを曖昧にしない

依存関係の再現性は、パッケージだけでは決まりません。Python 3.11と3.12では使える構文や標準ライブラリ、対応するwheelが異なる場合があります。requires-pythonをpyproject.tomlへ書き、必要なら.python-versionで開発時の基準を揃えます。

uvは既存のPythonを検出して使えるほか、必要なPythonを管理する機能も持ちます。環境によって勝手に違うPythonが選ばれていると感じたら、まずuv python listやプロジェクトのPython要件を確認します。いきなり全員のPythonを更新するのではなく、対象リポジトリのCI、依存ライブラリ、デプロイ環境が対応しているかを先に確かめます。

単発スクリプトとプロジェクトを混ぜない

一度だけデータを変換するスクリプトと、継続して保守するアプリケーションは、同じ重さで管理する必要がありません。単発スクリプトなら、スクリプト専用の依存関係を宣言して実行する方法もあります。反対に、複数ファイル、テスト、CI、複数人の作業があるなら、pyproject.tomlとlockfileを持つプロジェクトへ早めに移す方が安心です。

判断基準は「コード量」より「再実行する人の数」です。自分だけが今日一回動かす数十行なら軽い方法で十分です。来月の自分、別のメンバー、CIが同じ結果を再現する必要があるなら、プロジェクトとして残します。

依存関係を追加して実行する標準フロー

本文図2:依存追加、lockfile更新、環境同期、実行を一つの流れで扱う。

チーム開発で守りたい運用ルール

1. .venvは共有せず、設定とlockfileを共有する

仮想環境はOS、CPU、ローカルパスに依存するため、通常はGitへ入れません。共有するのはpyproject.tomlとuv.lockです。新しく参加した人はリポジトリを取得し、uv syncまたはuv runで自分の環境を再構築します。

2. 依存追加はコード変更と同じPRで行う

新しいimportを追加したのにlockfileが更新されていない、または依存だけ増えて使われていない、という状態はレビューしにくくなります。依存の理由、直接依存か開発用か、ライセンスや脆弱性確認が必要かを、コード変更と同じPRで見ます。

3. CIではlockfileを無視しない

CIで毎回新しく依存解決すると、ローカルと異なる組み合わせで通ってしまう可能性があります。CIはlockfileどおりに環境を作り、lockfileと設定がずれている場合に失敗させる方が、問題を早く見つけられます。具体的なCI設定はuvの公式ドキュメントと使うCI基盤の最新資料を確認してください。

4. 更新は小さく、理由を残す

「全部最新版にする」変更は、複数の不具合原因を一度に持ち込みます。脆弱性対応、Python対応、特定ライブラリの新機能利用など、目的を一つにして更新します。lockfile差分が大きい場合は、レビューを分けることも有効です。

workspaceが向く場面

Webアプリ、共通ライブラリ、CLIを一つのリポジトリで育てると、複数のpyproject.tomlが必要になります。uvのworkspaceは、複数パッケージをまとめ、共通の依存関係を一つのlockfileで扱うための仕組みです。uv runやuv syncはworkspaceのルートから扱え、特定パッケージを指定して実行することもできます。

ただし、最初からworkspaceにする必要はありません。パッケージが一つなら、通常のプロジェクトで十分です。複数パッケージが同じ変更で動き、依存関係を一緒に解決したい、という具体的な必要が出てから導入します。構造を先取りするより、リポジトリ内の実際の変更単位を観察して決める方が長続きします。

開発用ツールはアプリ依存と分けて考える

テスト、フォーマット、型検査に使うパッケージは、アプリケーションを実行する利用者に必ずしも必要ではありません。pytestやruffのような開発用ツールを追加するときは、プロジェクトが配布物なのか、社内専用アプリなのか、CIで何を実行するのかを考えます。

大切なのは、開発用だから記録しなくてよい、とは考えないことです。CIがpytestを実行するなら、その正確な組み合わせも再現できる必要があります。開発依存の置き方やグループの機能は更新されるため、導入時には公式ガイドの現在の記法を確認し、READMEへ日常コマンドを残します。

一方、誰かが一度だけ使うCLIをプロジェクトの依存へ追加する必要はありません。一時的なツール実行はuvxまたはuv tool runのような仕組みを検討できます。ただし、データを書き換えるCLIやネットワークへ送信するCLIは、便利だからと無条件に実行せず、対象ファイルとオプションを先に確認してください。

依存更新を安全に行う順番

依存関係は古くなりますが、更新を急ぐとアプリケーションの挙動、ライセンス、Python対応、ビルド環境が同時に変わることがあります。更新作業は次の順で小さく進めます。

  1. 更新理由を一つに絞る。脆弱性対応、バグ修正、新機能利用、Python対応などを混ぜない。
  2. 直接依存と、lockfileで一緒に変わる間接依存を確認する。
  3. lockfileを更新したら、通常のテスト、型検査、主要な実行経路を確認する。
  4. CIと本番に近い環境で差がないかを見る。OS依存のwheelや外部サービス接続は特に注意する。
  5. 変更理由と検証結果をPRへ残す。問題が起きたときに、どの更新が原因候補か追えるようにする。

この流れでは、uv.lockの行数を減らすことを目的にしません。差分が多くても理由が説明でき、テスト済みなら問題ありません。逆に差分が少なくても、Python要件やビルド設定が変わっていれば影響は大きいことがあります。

既存のpip環境から移るときの注意

既存プロジェクトを移行する場合は、現在の仮想環境をそのまま正解としてコピーしない方が安全です。まず、実際に必要な直接依存をrequirements.txt、import、CI設定、デプロイ設定から洗い出します。次に、pyproject.tomlへ意図した条件を置き、uvにlockfileを作らせます。

既存環境にだけ残っていた不要パッケージが消えることは、移行失敗ではなく健全な整理である場合があります。ただし、暗黙に入っていたビルドツールや実行時プラグインが抜ける可能性もあります。主要な起動、テスト、パッケージ作成、コンテナビルドを一つずつ確認し、失敗理由を依存関係へ戻します。大規模なリポジトリでは、一度に全サービスを移さず、1パッケージまたは1CIジョブから試すのが安全です。

よくあるつまずき

uv runが毎回環境を触るように見える

uv runは実行前に設定、lockfile、環境の整合を確認します。依存ファイルを編集した、lockfileを更新した、Pythonバージョンを変えた直後には同期が走ることがあります。意図しない変更が続く場合は、pyproject.tomlやlockfileを誰かが書き換えていないか、プロジェクト外の環境を参照していないかを確認します。

pip installと混ぜて環境が読めなくなった

緊急対応でpip installを直接使うことはありますが、そのまま終えると設定ファイルと実環境がずれます。何を入れたかを確認し、必要ならuv addとして設定へ戻します。戻せない状態なら、.venvを共有せず再同期して、再現できる状態へ戻す方が安全です。

lockfileの競合を適当に解消してしまった

lockfileは人が読むための設定ではなく、解決器の出力です。競合したら、まず両方のブランチで変わった直接依存を確認します。その後、意図した設定を採用してからlockfileを再生成します。テストを通すだけでなく、差分が不要に膨らんでいないかも確認します。

導入前チェックリスト

  • □ Python要件をpyproject.tomlで明示している
  • □ pyproject.tomlとuv.lockをGitで共有している
  • □ .venvをGitへ入れていない
  • □ 依存追加をuv add経由で記録している
  • □ 日常のテスト・スクリプトをuv runで実行できる
  • □ CIでもlockfileと設定の整合を確認する
  • □ 直接pip installした変更を設定へ戻す手順がある
  • □ Pythonバージョン更新はCIと配布先を確認してから行う
  • □ workspaceは実際に複数パッケージが必要になってから使う
  • □ lockfile競合は直接依存の意図から解消する

まとめ:環境構築を「手順」ではなく「成果物」として残す

uvの大きな価値は、パッケージを速く入れることだけではありません。Pythonのバージョン、依存条件、正確な解決結果、実行方法をリポジトリの成果物として残し、別のPCやCIでも同じ状態を作りやすくすることです。

最初はuv initで土台を作り、依存はuv add、日常の実行はuv runへ寄せてください。pyproject.tomlで意図を、uv.lockで結果を共有します。こうした小さなルールを先に決めておくと、Pythonプロジェクトが大きくなっても環境差分に振り回されにくくなります。

PythonでAIエージェントを作る基礎は「AIエージェントの作り方|Pythonで動かす最小構成」、MCPサーバーをプロジェクトとして育てる例は「PythonでMCPサーバーを作る|FastMCP入門」も参照してください。

参考資料

コメント

タイトルとURLをコピーしました