uvを使ってPython環境を効率的に管理！

# "uv"ってなに
UltraVioletのことではありません（やかましい）。Python のパッケージマネージャであり、 __pip や poetry の代替__ として開発されている高速なツールです。
以下の特徴があります：
- Rust 製で高速
- pip, venv, setuptools, wheel を統合的に扱える
- 仮想環境の作成・依存関係の解決が爆速

公式ドキュメントは以下です。ここでもこのドキュメントに沿って基本的な使い方を抑えます。

https://docs.astral.sh/uv/getting-started/

# インストール
### Linux・macOSの場合
~~~bash
curl -LsSf https://astral.sh/uv/install.sh | sh
~~~
筆者の場合、パスが通らなかったので、以下コマンドによりパスを通しました。

~~~bash
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
~~~

### Windowsの場合
~~~bash
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
~~~

### インストールの確認
バージョン確認のコマンドを入力して、通常レスポンスが返ってこればインストールに成功しています。
~~~bash
uv version
>> uv 0.6.17
~~~

# 仮想環境構築
### プロジェクトの作成
下記コマンドを実行すると、
~~~bash
uv init test
~~~

ディレクトリ名がtestとなる、プロジェクトディレクトリが作成され、Pythonを利用する開発に必要なファイルが一式用意されていることが分かります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4009423/6a438e2c-81ef-43c0-bafe-20c632498593.png)

__.python-versionについて__
pythonのバージョンが記載されるファイルです。testディレクトリに移動して、下記コマンドを実行すると、Pythonのバージョンが記録されていることが分かります。筆者のローカル環境にインストールされているPythonのバージョンが3.12であるため、デフォルトでは、自動的にデフォルト環境に合わせられることが分かります。
~~~bash
cat .python-version
>> 3.12
~~~

__pyproject.tomlについて__
作成したPythonプロジェクトのメタデータが記載されるファイルになります。後に記載しますが、ライブラリのインストールや削除によって、このファイルも自動的に書き換わります。
~~~
[project]
name = "test"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []
~~~


### 依存関係のインストール
requestライブラリをインストールしてみます
~~~bash
uv add requests
~~~
すると、testディレクトリ配下に.venvディレクトリとuv.lockファイルが出現します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4009423/b7743b10-9757-4426-bc11-547db44500c3.png)

__.venvディレクトリ__
このディレクトリに、作成したプロジェクトで利用する依存関係が入れられます。

__uv.lock__
プロジェクトの大まかな要件を指定するために使用される pyproject.toml とは異なり、このファイルには、プロジェクト環境にインストールされる解決済みの正確なバージョンが含まれます。このファイルはマニュアルで編集してはいけないようです。

~~~
version = 1
revision = 2
requires-python = ">=3.12"

[[package]]
name = "certifi"
version = "2025.4.26"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705, upload_time = "2025-04-26T02:12:29.51Z" }
wheels = [
    { url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload_time = "2025-04-26T02:12:27.662Z" },
]
～～～～～～～～～～～～
～～～～（中略）～～～～
～～～～～～～～～～～～
[[package]]
name = "requests"
version = "2.32.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
    { name = "certifi" },
    { name = "charset-normalizer" },
    { name = "idna" },
    { name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload_time = "2024-05-29T15:37:49.536Z" }
wheels = [
    { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload_time = "2024-05-29T15:37:47.027Z" },
]

[[package]]
name = "test"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
    { name = "requests" },
]

[package.metadata]
requires-dist = [{ name = "requests", specifier = ">=2.32.3" }]

[[package]]
name = "urllib3"
version = "2.4.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672, upload_time = "2025-04-10T15:23:39.232Z" }
wheels = [
    { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload_time = "2025-04-10T15:23:37.377Z" },
]

~~~

※ project.tomlの依存関係も更新されています。
~~~
[project]
name = "test"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
    "requests>=2.32.3",
]
~~~

#### ライブラリの削除および更新
~~~bash
// 削除
uv remove requests
// 更新
uv lock --upgrade-package requests
~~~

# スクリプトの実行
flaskを仮想環境にインストール（uv add flask）したのち、以下のスクリプトを実行することにします。

~~~python
import flask

def main():
    print("Hello from test!")


if __name__ == "__main__":
    main()
~~~

~~~bash
uv run main.py
>> Hello from test!
~~~

flaskの依存関係が解決されており、正常に実行できたことが分かります。

# Pythonのバージョン管理について
結論から申し上げると、**pythonのバージョン管理が可能で、すでにあるpythonも新規のpythonも簡単に利用可能**です。
まず、利用できるPythonランタイムの一覧を表示しましょう。以下のコマンドを実行すると、
~~~bash
uv python list
~~~
uvで利用できる、Pythonランタイムがすべて表示されます。download availableと記載のあるものは、現在のローカル環境にはないが、インストールすれば利用できるもの、パスの記載のあるものは、すでに存在していて、パスを指定すればそのまま利用できるものになります。
~~~
cpython-3.14.0a6-linux-x86_64-gnu                 <download available>
cpython-3.14.0a6+freethreaded-linux-x86_64-gnu    <download available>
cpython-3.13.3-linux-x86_64-gnu                   <download available>
cpython-3.13.3+freethreaded-linux-x86_64-gnu      <download available>
cpython-3.12.10-linux-x86_64-gnu                  <download available>
cpython-3.12.9-linux-x86_64-gnu                   miniconda3/bin/python3.12
cpython-3.12.9-linux-x86_64-gnu                   miniconda3/bin/python3 -> python3.12
cpython-3.12.9-linux-x86_64-gnu                   miniconda3/bin/python -> python3.12
cpython-3.12.3-linux-x86_64-gnu                   /usr/bin/python3.12
cpython-3.12.3-linux-x86_64-gnu                   /usr/bin/python3 -> python3.12
cpython-3.11.12-linux-x86_64-gnu                  .local/share/uv/python/cpython-3.11.12-linux-x86_64-gnu/bin/python3.11
cpython-3.10.17-linux-x86_64-gnu                  <download available>
cpython-3.9.22-linux-x86_64-gnu                   <download available>
cpython-3.8.20-linux-x86_64-gnu                   <download available>
cpython-3.7.9-linux-x86_64-gnu                    <download available>
pypy-3.11.11-linux-x86_64-gnu                     <download available>
pypy-3.10.16-linux-x86_64-gnu                     <download available>
pypy-3.9.19-linux-x86_64-gnu                      <download available>
pypy-3.8.16-linux-x86_64-gnu                      <download available>
pypy-3.7.13-linux-x86_64-gnu                      <download available>
~~~

例えば、cpython-3.14.0a6-linux-x86_64-gnuを利用した仮想環境を構築したい場合、以下のコマンドを実行すると、指定のPythonランタイムがダウンロードされます。
~~~bash
uv init --python cpython-3.14.0a6-linux-x86_64-gnu test1
>> Initialized project `test1` at `/home/pepper/test1`
~~~

以下のように、python3.14を利用して、仮想環境が構築できていることが分かります。
~~~bash
cat .python-version 
>>3.14
~~~

一方、miniconda3/bin/python3.12というように、パスで指定すると、そのpythonランタイムがそのまま利用されます。
~~~bash
uv init --python miniconda3/bin/python3.12 test2
>> Initialized project `test2` at `/home/pepper/test2`
~~~

以下のように、python3.12を利用して、仮想環境が構築できていることが分かります。
~~~bash
cat .python-version 
>>3.12
~~~