🐍

uvはどのようにPythonのバージョンやパッケージを管理しているのか?初心者から上級者まで使えるPythonの環境構築ツールのしくみ

に公開

はじめに

uvはPythonの環境構築を非常にシンプルにしてくれます。

これまで、Pythonのパッケージ管理とPython本体そのものの管理を同時に行うことができる決定打がなかったのですが、uvはその両方を同時に満たしてくれます。
しかもRustで書かれているので、非常にサクサクと動作します。

これまでpyenvやvirtualenv、pipenv、anaconda、poetry、rye[^uvと同じ作者が開発していた前身rのツールです]など様々なツールが開発されてきましたが、これらの上位互換として使うことができます。インストールも使い方も簡単ですので、これからPythonを始める方にもおすすめです。

uvそのものは別の記事で紹介しています。

https://zenn.dev/nobkat/articles/004uv-package-management-tool

この記事では、uvがどのようにPythonのバージョンやパッケージを管理しているのかを紹介します。

uv initすると作成されるファイル

uv initを実行すると、カレントディレクトリ内に以下のようなファイルが生成されます。
(uv init my-project-dir)とプロジェクト名を指定した場合には、my-project-dirディレクトリ内に生成されます。

.
├── .git/ # ⑤
├── .gitignore # ⑤
├── .python-version # ④
├── README.md # ②
├── main.py  # ①
└── pyproject.toml # ③

①から番号順に説明します。

main.pyはuvで管理するプロジェクトのメインのPythonファイルのテンプレートです。簡単なサンプルコードが入っています。

def main():
    print("Hello from my-project-dir!")


if __name__ == "__main__":
    main()
uv run main.py

で実行することができます。uvを使用している場合には、python main.pyの代わりのこのコマンドでpythonを実行します。このようにすることで、uvが管理しているPythonのバージョンとパッケージ環境下でmain.pyが実行されることになります。

このとき下で説明する.python-versionで指定されたPython本体がもしインストールされていない場合には、自動でそのバージョンのPythonがインストールされます。数分時間ががかかるかもしれませんが気長に待ちます。

README.mdはプロジェクトの説明を記載するためのファイルです。初期状態では何も書かれていません。

pyproject.tomlはuvがプロジェクトのパッケージや依存関係を管理するための設定ファイルです。以下のような内容になっています。(以降で行うnumpyをインストールした場合の例です。)

[project]
name = "uv-test"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []

pyproject.tomlはPythonのパッケージ管理の標準的なファイルで、プロジェクトの設定が記述されています。簡単なプロジェクトで関係するのは、requires-pythondependenciesの部分です。
requires-pythonはプロジェクトが必要とするPythonのバージョンを指定します。ここではPython 3.12以上が必要であることを示しています。
dependenciesはプロジェクトが依存するパッケージを指定します。まだ何もパッケージをインストールしていないので、空になっています。

.python-versionはuvが使用するPythonのバージョンを指定するためのファイルです。uvはこのファイルを参照して、プロジェクトで使用するPythonのバージョンを決定します。初期状態では以下のようになっています。

3.12

.git/.gitignoreはgitでプロジェクトを管理するためのファイルです。gitはコードのバージョン管理システムで、プロジェクトの履歴を管理するために使用されます。すでに使っている方も多いと思いますが、初心者の方もご覧になっているかもしれないので簡単に説明します。

.git/ gitリポジトリの管理用ディレクトリです。(ドットで始まるディレクトリは環境によっては隠しディレクトリになっていて見れません。ls -aなどで確認できます。)

.gitignoreの中身は以下のようになっています。

__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info

# Virtual environments
.venv

このファイルは、gitで管理しないファイルやディレクトリを指定するためのものです。例えば、Pythonのキャッシュファイルやビルド成果物、この後に説明する仮想環境のディレクトリ.venvなどをgitで管理しないように指定しています。

パッケージをインストールしてみる

ここで試しに、uvを使ってパッケージ(Numpy)をインストールしてみましょう。

uv add numpy

このコマンドを実行すると、以下のように表示され、Numpyがインストールされます。(執筆時点の最新バージョンである2.2.6がインストールされました。)

パッケージのインストールといえばpip installを思い浮かべる方も多いと思いますが、uv addはその上位互換だと思っていただければ結構です。インターネット上でパッケージをインストールするコマンドとしてpip installと書かれている部分は全てuv addに置き換えていただければ、問題ありません。

Using CPython 3.12.3
Creating virtual environment at: .venv
Resolved 2 packages in 235ms
Installed 1 package in 30ms
 + numpy==2.2.6

'uv addコマンドを実行すると、まず、pyproject.toml`が以下のように更新されます。

[project]
name = "uv-test"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
    "numpy>=2.2.6",
]

dependenciesにnumpy>=2.2.6が追加されました。これで、プロジェクトがNumpyに依存していることが明示されました。注意したいのはインストールされているバージョンではなく、このプロジェクトを実行するために必要なバージョンが記載されていることです。現時点では2.2.6をインストールしましたが、少なくとも今後、このプロジェクトを他の環境で実行する場合は2.2.6以上のバージョンであれば、動作するはずですので、numpy>=2.2.6と記載されています。もし、2.2.6を必ず使いたければ、numpy==2.2.6と記載することもできます。

pipをを使ってパッケージをインストールしたときには、インストールされたパッケージやバージョンはどこにも記載されません。したがって、新しい環境で同じパッケージをインストールするためには、requiements.txtなどのファイルを作成して、そこにインストールされたパッケージとバージョンを記載する必要がありました。しかし、uvではpyproject.tomlに必要なパッケージがバージョンとともに記載されるため、同じプロジェクトを他の環境で実行する際にも、同じパッケージがインストールされることが保証されます。

また、uv.lockというファイルも作成されます。このファイルは、実際にインストールされたパッケージのバージョンを記録するためのものです。以下のような内容になっています。

name = "numpy"
version = "2.2.6"
source = { registry = "https://pypi.org/simple" }

...長いので略...

このファイルは、プロジェクトが依存するパッケージのバージョンを固定するために使用されます。これにより、プロジェクトを他の環境で実行する際に、完全に同じバージョンのパッケージがインストールされます。

なお、逆にパッケージを削除したい場合は、uv remove numpyのようにremoveコマンドを使います。これにより、pyproject.tomluv.lockから該当のパッケージが削除されます。

パッケージはどこにインストールされるのか?

uvはパッケージをプロジェクトごとに.venvディレクトリ内(ls -aで見ることができる隠しディレクトリです)にインストールします。これにより、プロジェクトごとに異なるバージョンのパッケージを使用することができます。これを仮想環境といいます。実際に.venvの中身を見てみます。

ls .venv/lib/python3.12/site-packages

と実行すると、以下のようにNumpyがインストールされていることが確認できます。

_virtualenv.pth       _virtualenv.py        numpy                 numpy-2.2.6.dist-info

コード内でimport numpyと記述すると、この.venv/lib/python3.12/site-packages内のNumpyがインポートされることになります。また、.venvディレクトリはプロジェクトごとに作成されるため、他のプロジェクトで使用しているパッケージやバージョンとは干渉しません。

.venvディレクトリはuv syncによって自動的に作成・更新されるので、このフォルダごと削除してしまっても、再度uv syncを実行すれば、必要なパッケージが再インストールされます。したがって、gitなどでプロジェクト管理している場合には、.gitignore.venvを追加してパッケージはgitで管理しないようにするのが一般的です。

なお、この.venvディレクトリで仮想環境を管理するという方法は、venvというPython標準の仮想環境構築モジュールと互換性があります。実際に. .venv/bin/activateと入力すると、仮想環境内に入ることができます。uvはこのvenvを拡張してよりプロジェクトを管理しやすくしているということもできます。

Python本体はどこにあるのか?

先ほど、.python-versionに使用するPython本体のバージョンが指定されていると書きましたが、uv python pinコマンドを使用すると、使用するPython本体のバージョンを変更することができます。例えば、uv python pin 3.13と実行すると、.python-versionが3.13に更新され、さらに指定されたバージョンがインストールされていなければ、uvが自動でそのバージョンのPythonをインストールします。

それではインストールされたPython本体はどこにあるのでしょうか?実際に確認してみましょう。

ls -l .venv/bin/
...途中略...

lrwxr-xr-x  1 hoge  staff    87 Jun  1 16:21 python -> /Users/hoge/.local/share/uv/python/cpython-3.12.3-macos-aarch64-none/bin/python3.12

...途中略...

/Users/hoge/.local/share/uv/python/以下にインストールされていることがわかります(ディレクトリは環境によって異なります)。ここには、Pythonのバージョンごとにディレクトリが作成され、その中にPython本体が格納されています。そして.venv/bin/内には、Python本体へのシンボリックリンクが作成されます。これにより、uvは指定されたバージョンのPythonを参照することができます。

uv python dirコマンドでもインストールされているPython本体のディレクトリを確認することができます。

もし、指定したバージョンがたまたまHomebrewなどでシステムにインストールしたPythonのものと一致する場合にはuvは新しくPythonをインストールせずに、Homebrewでインストールしたものをへのシンボリックリンクを~/.local/share/uv/python/に作成します。これにより、uvはシステム全体でインストールされているPythonを参照することができます。

uv python list --only-installedコマンドを実行すると、システムにインストールされていてuvが使用することができるPythonのバージョン一覧を確認することができます。

まだまだあるuvの便利機能

uv pip 〜

uv環境内でpipコマンドのようなことをすることができます。例えばuv pip freezeと実行すると、pip freezeのように現在のプロジェクトでインストールされているパッケージの一覧を表示することができますし、pip pip sync requirements.txtのようにuv pip sync requirements.txtと実行すると、requirements.txtに記載されたパッケージをインストールすることができます。これまでパッケージ管理をrequirements.txtに頼っていた方は、これですぐにuvに移行することができます。

uv tool

共通で使うコマンドを管理することができます。flake8やblack、最近ではruffなどのリンター(コードを解析してエラーを未然に防ぐツール)やフォーマッター(コードを読みやすく整形する)等のツールを使っている方もいらっしゃると思います。これらはどのプロジェクトでも共通して使うものですので、それぞれのプロジェクト内の.envvインストールするのは非効率です。

uvにはそのようなツールをまとめて管理するしくみがあります。uv tool installでパッケージをインストールすると、どの仮想環境下でもそのパッケージを使用することができます。パッケージは/Users/hoge/.local/share/uv/toolsにインストールされます。実行する際にはuv tool run 〜のようにします。

ちなみにruffもuvを開発しているAstralが開発しているツールで、リンター兼フォーマッターです。Rustで書かれているので非常に動作が速く、インストールも簡単なのでおすすめです。

uv build / uv publish

パッケージをビルドして、PyPI (The Python Package Index) 等に公開することができます。
PyPIに公開すると、世界中からpipコマンド(もちろんuv addでも!)でインストールすることができます。

まとめ

本記事ではuvがどのようにPythonのバージョンやパッケージを管理しているのかを紹介しました。uvはプロジェクトごとにPythonのバージョンとパッケージを管理するための強力なツールです。それと同時に、Pythonの標準的なツールであるvenvpipと互換性があり、既存のPythonプロジェクトにも簡単にもスムーズに導入することができます。

Python本体そのものの管理もシンプルになります。もはや他の管理ツールをつかう理由がなくなったといってもよいでしょう。

詳しい使い方やドキュメントは公式サイトを参照してください。

https://docs.astral.sh/uv/

Discussion