Pythonのuvとpip・venvの違い|既存プロジェクトを移行する手順

pip・venvからuvへの移行手順

pip・venvからuvへの移行手順

uv(Pythonのパッケージ・プロジェクト管理ツール)は、パッケージ導入だけでなく、仮想環境やプロジェクトの依存管理まで扱えるツールです。既存の requirements.txt を維持して uv pip を使う方法と、pyproject.tomluv.lock で管理する方法があり、変更される範囲が違います。この記事では両者を比較し、小さな既存プロジェクトを移行して、同じテストが通るところまで確認します。

検証日は2026年9月10日です。Linux(WSL2)上のuv 0.11.19、Python 3.12.3で確認しており、Windows・Macでの実行、速度比較、社内パッケージ配布先やネイティブ拡張の移行は検証していません。

uv・pip・venvは何を担当する?

venvはPython標準の仮想環境作成機能です。仮想環境は、プロジェクトごとにパッケージの導入先を分ける仕組みです。pipはパッケージを導入するツールで、従来の作業ではvenvで環境を作り、その環境のpipで依存を入れることが多くあります。Python:venv

uvには、pipに近いコマンド群と、プロジェクト全体を管理するコマンド群があります。uv公式

作業 pip・venv中心 uvのpipインターフェース uvプロジェクト
仮想環境を作る python -m venv .venv uv venv uv syncuv run が必要に応じて作成
パッケージを入れる python -m pip install uv pip install uv add で依存宣言と環境を更新
依存の宣言 requirements等を運用に応じ管理 既存requirementsを維持できる pyproject.toml
解決済みバージョンの管理 固定したrequirements等を用意 uv pip compile等を必要に応じ使用 uv.lock
コマンド実行 対象環境のPythonを使う 対象環境のPythonを使う uv run

uvはvenvの存在をなくすわけではなく、通常は .venv という仮想環境を使います。違いは、その作成や依存との同期をどのコマンドで管理するかです。uv:Using environmentsuv:Structure and files

どこまで移行するかを先に決める

方法 向いている状況 得られること 変更・確認が必要なこと
requirementsを維持して uv pip を使う チームや配布先がrequirementsを前提にしている 既存ファイルでuvの導入処理を使える pipとの設定・挙動差、実行するPython
uvプロジェクトへ移る 依存宣言・ロック・実行をまとめたい pyproject.tomluv.lock を基準に再構築できる 依存の分類、日常コマンド、CIや配布手順

requirementsを維持する方法とuvプロジェクトへ移る方法の2経路

図はuv公式資料を基に独自作成。上下は順番ではなく、変更範囲が異なる二つの選択肢です。

uv pip install を使い始めただけでは、プロジェクトが uv.lock で管理されるようにはなりません。管理ファイルまで切り替えるかを決めると、作業範囲を把握しやすくなります。

また、uvはpipの完全な複製ではありません。たとえば pip.confPIP_INDEX_URL などpip固有の設定は、そのまま読み込まれません。社内の配布先、認証、複数のパッケージインデックス(パッケージ取得先)を使う場合は、uvの互換性資料設定ファイルの説明を先に確認してください。

移行前に、元の環境と依存ファイルを残す

実プロジェクトでは、作業ブランチや別の作業コピーを用意し、元の仮想環境を残したまま試します。この記事の例は、pyproject.tomlがまだない、小さなスクリプト中心のプロジェクトです。既存の pyproject.tomlsetup.py、パッケージのビルド設定がある場合は、それらを上書きせず現在の設定を確認してください。

控えるものは、Pythonの版、起動コマンド、既存テストの結果、依存ファイルです。pip freeze は導入済みパッケージの記録に使えますが、依存関係を解決して新しいロックを生成するコマンドではありません。pip:freeze

requirementsには、主に二つの性格があります。

中身 移行時の考え方
アプリが直接使う依存を手書きしたもの uv add -r の入力にしやすい
pip freeze や依存解決ツールから出した全パッケージ一覧 直接依存と間接依存を区別してから取り込む

間接依存は、ほかのパッケージが必要とするパッケージです。freezeした一覧を丸ごと uv add -r へ渡すと、それらも自分のプロジェクトが直接要求する依存として記録されます。動いたとしても、後で削除や更新の判断が難しくなります。

requirements.in に直接依存、requirements.txt に固定済みの全依存がある場合は、公式ガイドにある次の形が使えます。

# 直接依存の宣言を取り込み、移行時の版変更を既存の固定値で制約する。
uv add -r requirements.in -c requirements.txt

これは後述の初期化後に実行する選択肢です。OS別に作った固定ファイルや、Git・ローカルパス依存、開発用ファイル内の -r の入れ子がある場合は、単純な一括取り込みから分けて確認します。uv:pipからプロジェクトへの移行

requirementsを維持してuv pipを使う場合

uv自体の導入は公式のインストール手順でOSに合う方法を選び、uv --version を確認します。以下はLinuxのBashで、新しい作業コピーに仮想環境を作る例です。同名の環境がすでにある場所では、そのまま実行しないでください。

# 元の環境を残すため、新しい作業コピーで環境を作る。
uv venv --python 3.12.3 .venv
uv pip install --python .venv/bin/python -r requirements.txt -r requirements-dev.txt
.venv/bin/python -m pytest -q

--python で導入対象を明示しています。仮想環境のactivateは必須ではなく、その環境のPythonを直接実行できます。WindowsのPowerShellではPythonのパスが .venv\Scripts\python.exe になりますが、この記事の実行検証はLinuxで行っています。

uv pip は環境内にpip本体が入っていなくても動きます。そのため、uvで作った環境で python -m pip が見つからなくても、直ちに環境作成の失敗とは限りません。uv:Using environments

この方法を採るなら、以降もrequirementsを正として運用します。次のuvプロジェクト方式とは、別の移行経路です。

既存プロジェクトをuvプロジェクトへ移行する

1. 移行前の小さなプロジェクトを確認する

今回のサンプルは、バージョン文字列を比較するアプリです。通常の文字列比較では誤ることがある 1.101.2 を、packaging で比較します。テストには、大小・同値・リリース候補版の3ケースを用意しました。

project/
├── app.py
├── test_app.py
├── requirements.txt
└── requirements-dev.txt

requirements.txt は次の1行です。

packaging==24.2

requirements-dev.txt には開発時だけ使うpytestを置きます。このサンプルでは、本体のrequirementsを再取り込みする -r は入っていません。

pytest==8.3.5

版は移行前後の条件をそろえるために固定したもので、最新版の推奨ではありません。

"""Version comparison example used to verify a dependency migration."""
import logging
from packaging.version import Version


def is_newer(candidate: str, current: str) -> bool:
    """Compare package versions rather than their text order.

    Args:
        candidate: Version to evaluate.
        current: Reference version.
    Returns:
        True if candidate is newer.
    Raises:
        packaging.version.InvalidVersion: Either input is not a valid version.
    Example:
        >>> is_newer("1.10", "1.2")
        True
    """
    return Version(candidate) > Version(current)


if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")
    # 数字の文字列比較では順序が変わるため、バージョンとして評価する。
    logging.info("1.10 is newer than 1.2: %s", is_newer("1.10", "1.2"))

テストは次のとおりです。移行後も同じコードを使います。

"""Behavior checks retained before and after migrating dependencies."""
import pytest
from app import is_newer


@pytest.mark.parametrize("candidate,current,expected", [
    ("1.10", "1.2", True),
    ("1.0", "1.0", False),
    ("1.0rc1", "1.0", False),
])
def test_version_order(candidate: str, current: str, expected: bool) -> None:
    """Check ordering across dependency migration.

    Args:
        candidate: Candidate version string.
        current: Reference version string.
        expected: Expected comparison result.
    Returns:
        None.
    Raises:
        AssertionError: Comparison changed unexpectedly.
    Example:
        Run `python -m pytest -q` from the example directory.
    """
    assert is_newer(candidate, current) is expected

移行前は、元の仮想環境のPythonで依存を入れ、python -m pytest -qpython app.py を確認しておきます。検証では仮想環境の作成とpipの用意に uv venv --seed を使い、その環境のpip 26.2.1で依存を導入して3ケースが通りました。比較したのは依存の導入・管理方式で、venv作成コマンドの性能ではありません。

2. 新しい作業コピーを初期化する

既存ファイルを残した作業コピーで、次を実行します。すでに別の仮想環境をactivateしている場合は、そのシェルの環境を解除してから進めると、移行元と移行先を取り違えにくくなります。

# 既存のapp.pyなどを残し、プロジェクト設定だけを作成する。
uv init --bare --python 3.12 --vcs none
uv python pin 3.12.3

--bare は最小の pyproject.toml を作る指定です。--vcs none はこの初期化で新しいGit管理を作らないための指定で、既存Gitリポジトリを削除するものではありません。uv:CLI reference

.python-version は開発に使うPythonの指定、requires-python はプロジェクトが要求するPythonの範囲です。役割が違うため、同じ値に見えるかだけで判断しないでください。必要なPythonがない場合、uvの設定によってはPythonをダウンロードします。自動取得を避けたい環境では、利用可能なPythonを準備してから進めます。uv:Structure and files

3. 本体と開発用の依存を分けて取り込む

# pytestを本体の実行依存に混ぜないよう、開発用グループへ入れる。
uv add -r requirements.txt
uv add --dev -r requirements-dev.txt

このサンプルでは、直接依存だけを書いたファイルから取り込みます。uv add は宣言を更新するだけでなく、通常はロックと環境も更新します。開発依存は dependency-groupsdev に入り、既定で同期対象になります。uv:Managing dependencies

生成された設定では、本体と開発用の依存が次のように分かれました。これは依存部分の抜粋です。

[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["packaging==24.2"]

[dependency-groups]
dev = ["pytest==8.3.5"]

実際の requires-python は初期化時の指定や既存設定に合わせて確認してください。今回の比較は .python-version で3.12.3へそろえています。

4. ロック・同期・実行を確認する

# ロックの不足や不整合を自動更新で隠さず確認する。
uv lock --check
uv sync --locked
uv run --locked python -m pytest -q
uv run --locked python app.py

uv.lock は解決したバージョンなどの記録です。uv sync はその記録に従って環境をそろえ、uv run はプロジェクトの環境でコマンドを実行します。通常の uv run は必要に応じてロックと同期も行います。uv:Locking and syncing

依存宣言・解決結果・仮想環境・コード実行の関係

図はuv公式資料を基に独自作成した役割の模式図です。実際の uv adduv run は、複数の工程を自動で行う場合があります。

移行後に「もう一度作れるか」を確認する

既存の .venv をコピーせず、コード・テスト・pyproject.tomluv.lock.python-version を別の新しいフォルダーへ置き、同じ同期とテストを実行しました。

検証段階 Python テスト アプリ結果
pipで導入した移行前環境 3.12.3 3件成功 1.101.2 より新しい
requirementsを維持したuv pip環境 3.12.3 3件成功 同じ
uvプロジェクトへ移行後 3.12.3 3件成功 同じ
仮想環境のないフォルダーから再構築 3.12.3 3件成功 同じ

最後の再構築では、取得済みのローカルキャッシュを使う --offline も付けて確認しました。空のキャッシュからオフライン導入できることを示す結果ではありません。

Linux環境に入った4パッケージの版も比較し、packaging 24.2pytest 8.3.5iniconfig 2.3.0pluggy 1.6.0 が一致しました。速度は比較していません。この小さなサンプルの成功は、社内パッケージやコンパイルが必要な依存を含む実プロジェクトの成功保証にはなりません。

移行後に混同しやすいコマンド

コマンド 主な目的 注意点
uv add パッケージ名 プロジェクトの依存を追加 宣言とロックへ記録する
uv pip install パッケージ名 環境へ直接導入 プロジェクトの依存宣言へは追加しない
uv sync 宣言・ロックに環境を合わせる 既定では不要なパッケージを削除する
uv run プロジェクトの環境で実行 既定では必要な同期を行うが、余分なパッケージは削除しない

.venv へ手作業で入れたものに依存していると、uv sync で消えて実行できなくなることがあります。必要な依存は uv add などで宣言へ反映し、空の環境から試すと、この不足を見つけやすくなります。

--locked--frozen にも違いがあります。

指定 ロックの整合性 用途の考え方
--locked 更新が必要ならエラー 宣言とロックの不整合を検出したい
--frozen 最新かを確認せず既存ロックを使う 宣言との整合性確認を省く意図がある場合

CI(変更ごとにテストなどを自動実行する仕組み)で移行を検証するなら、まず --locked で宣言とロックの対応を確認できます。今回、本番のCI環境自体は変更・実行していません。uv:同期とロックのオプション

requirementsが必要な配布先と、移行を戻す場合

配布先がrequirements形式を要求するなら、uv.lock から出力できます。次は開発依存とプロジェクト自身を除いた出力例です。

# 配布先の入力を生成物にし、依存の正本を二重管理しない。
uv export --locked --no-dev --no-emit-project --format requirements.txt --output-file requirements-export.txt

サンプルではpytestを含まず、packagingの固定バージョンとハッシュが出力されました。ライブラリ自体をインストールする構成では、--no-emit-project が適切かも含め配布手順を確認してください。uv:Locking and syncing

uvプロジェクト方式を採用したら、依存の宣言は pyproject.toml、解決結果は uv.lock を正本にします。requirements出力を手で直す運用と混ぜると、どちらが正しいか分からなくなります。

移行を見送る場合は、残しておいた元の作業コピーと仮想環境へ戻します。移行を採用する場合も、Gitへ保存するのはコード・設定・ロックなどで、.venv 自体は共有せず再作成します。元のrequirementsは、CIや配布先の参照を切り替え終えるまでは残してください。

インストール先のPythonが違うと、uvへの移行以外でも不具合の原因になります。具体例はPyTorchでGPUを認識しないときの確認手順でも整理しています。

コメント

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