skip to content
barorin&

UbuntuでuvとRuffを使う|インストールからプロジェクト管理・lintまで

/ 21 min read

Table of Contents

はじめに

Pythonの環境構築は、venv で仮想環境を作り、pip でパッケージを入れ、black と flake8 と isort を別々に設定して……という手順が長らく定番でした。

これを uv(パッケージ・環境管理)と Ruff(linter・formatter)の2つに置き換えると、手数がかなり減ります。どちらもRust製で、同じ Astral 社が作っています。

この記事では、Ubuntuに両方を入れて実際に使うところまでを一通りまとめました。

検証環境は次のとおりです。

項目 バージョン
OS Ubuntu 24.04 LTS
uv 0.12.9
Ruff 0.16.6
Python 3.12.3(システム)

uvとRuffは何を置き換えるのか

先に対応関係を整理しておきます。

従来のツール 置き換え先 役割
pip uv add / uv pip パッケージのインストール
venv uv が自動管理 仮想環境の作成
pip-tools / poetry lock uv lock 依存のロック
pyenv uv python Pythonのバージョン管理
pipx uv tool / uvx CLIツールの導入
flake8 / pylint ruff check lint
black ruff format フォーマット
isort ruff check(I ルール) import順の整列

設定ファイルも pyproject.toml 1つにまとまります。setup.cfg と .flake8 と .isort.cfg を行き来する必要はありません。

uvをインストールする

公式インストーラを使う

Ubuntu 24.04 の apt には uv のパッケージがありません(apt-cache policy uv を叩くと何も出ません)。公式のインストールスクリプトを使うのが確実です。

Terminal window
curl -LsSf https://astral.sh/uv/install.sh | sh

~/.local/bin/uv と ~/.local/bin/uvx が置かれ、シェルの設定ファイル(~/.bashrc など)に PATH を通す行が追記されます。

インストール直後は現在のシェルに反映されていないので、読み込み直します。

Terminal window
source ~/.bashrc
uv --version
uv 0.12.9

pipxを使う場合

apt で管理されたパッケージとして入れたいなら、pipx 経由という手もあります。pipx は Ubuntu 24.04 の universe リポジトリにあります。

Terminal window
sudo apt install pipx
pipx ensurepath
pipx install uv

ただし、この方法だと後述の uv self update が使えません(pipxが管理しているため)。更新は pipx upgrade uv になります。特別な理由がなければ公式インストーラのほうが素直です。

更新とアンインストール

Terminal window
# uv自身を最新版に更新
uv self update
# アンインストール
uv cache clean
rm -rf ~/.local/share/uv
rm ~/.local/bin/uv ~/.local/bin/uvx

Ruffをインストールする

Ruffの入れ方は3通りあり、用途によって使い分けます。

方法 コマンド 向いている場面
プロジェクトの開発依存 uv add --dev ruff チームで同じバージョンを揃えたいとき
グローバルツール uv tool install ruff どのディレクトリでも ruff を叩きたいとき
都度実行(インストールなし) uvx ruff check . 一度きりの確認

普段のプロジェクト作業では uv add --dev ruff が基本です。バージョンが pyproject.toml と uv.lock に記録されるので、「自分の環境では通るのにCIで落ちる」が起きません。

グローバルに入れる場合は次のとおりです。

Terminal window
uv tool install ruff
ruff --version
ruff 0.16.6

uvx ruff は、その場でダウンロードして実行し、環境を汚しません。キャッシュが効くので2回目以降は一瞬です。

uvでプロジェクトを作る

uv init

Terminal window
uv init myproject
cd myproject

生成されるのはこれだけです。

myproject/
├── .gitignore
├── .python-version
├── README.md
├── pyproject.toml
└── src/
└── myproject/
└── __init__.py

pyproject.toml の中身です。

[project]
name = "myproject"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = []
[build-system]
requires = ["uv_build>=0.12.9,<0.13.0"]
build-backend = "uv_build"

Pythonのバージョンを指定したい場合は --python を渡します。

Terminal window
uv init myproject --python 3.12

指定したバージョンが .python-version に書き出され、以降このディレクトリではそのバージョンが使われます。

パッケージを追加する

Terminal window
uv add pandas
Using CPython 3.12.3 interpreter at: /usr/bin/python3.12
Creating virtual environment at: .venv
Resolved 6 packages in 261ms
Installed 4 packages in 164ms
+ numpy==2.5.2
+ pandas==3.0.5
+ python-dateutil==2.9.0.post0
+ six==1.17.0

.venv の作成は自動です。python -m venv .venv も source .venv/bin/activate も要りません。ここが pip との一番大きな違いです。

uv add は3つのファイルを同時に更新します。

  • pyproject.toml … dependencies に追記
  • uv.lock … 依存ツリー全体をハッシュ付きで固定
  • .venv/ … 実体のインストール

開発時だけ使うものは --dev を付けます。

Terminal window
uv add --dev ruff

pyproject.toml はこうなります。

[project]
dependencies = [
"pandas>=3.0.5",
]
[dependency-groups]
dev = [
"ruff>=0.16.6",
]

削除は uv remove pandas です。

実行する

Terminal window
uv run main.py
Hello from myproject!

uv run は、実行前に uv.lock と .venv の同期を毎回確認します。pyproject.toml を手で書き換えた直後でも、uv run を叩けば足りないパッケージが入ってから実行されます。

仮想環境のPythonを直に使いたいときは uv run python です。

Terminal window
uv run python
uv run python -c "import pandas; print(pandas.__version__)"

依存関係を確認する

Terminal window
uv tree
myproject v0.1.0
└── pandas v3.0.5
├── numpy v2.5.2
└── python-dateutil v2.9.0.post0
└── six v1.17.0

どのパッケージが何を連れてきたかが一目で分かります。pip list では出ない情報です。

別マシンで環境を再現する

pyproject.toml と uv.lock をリポジトリに入れておけば、クローン先では次の1行で済みます。

Terminal window
uv sync

CIなど、ロックファイルを絶対に更新させたくない場面では --frozen を付けます。

Terminal window
uv sync --frozen

ロックファイルが pyproject.toml と食い違っていればエラーで止まるので、意図しない依存の更新を検出できます。

requirements.txt が必要なとき

デプロイ先が requirements.txt しか受け付けない場合は、ロックファイルから書き出せます。

Terminal window
uv export --format requirements.txt --no-dev --no-hashes -o requirements.txt
pandas==3.0.5
# via myproject
python-dateutil==2.9.0.post0
# via pandas
six==1.17.0
# via python-dateutil

--no-hashes を外すと各パッケージの sha256 が付きます。改ざん検証が必要ならそちらを使ってください。

逆に、既存の requirements.txt から移行するときはこうです。

Terminal window
uv init --bare
uv add -r requirements.txt

--bare は pyproject.toml だけを作るオプションで、既存のディレクトリ構成を壊しません。

Pythonのバージョンを切り替える

uvは pyenv の役割も持っています。ビルドではなくビルド済みバイナリを落としてくるので、数秒で終わります。

Terminal window
# 使えるバージョンの一覧
uv python list
# 3.13をインストール
uv python install 3.13
Downloading cpython-3.13.15-linux-x86_64-gnu (33.2MiB)
Downloaded cpython-3.13.15-linux-x86_64-gnu
Installed Python 3.13.15 in 1.41s

pyenv install のようにソースからコンパイルしないため、libssl-dev や zlib1g-dev といったビルド依存を先に apt で入れる必要もありません。

一時的に別バージョンで動かすなら --python です。

Terminal window
uv run --python 3.13 python -V
Python 3.13.15

プロジェクトのバージョンを固定するときは .python-version に書くか、uv python pin 3.13 を実行します。

使い捨てスクリプトを書く

**1ファイルで完結するスクリプトなら、プロジェクトを作らずに依存を書けます。**PEP 723 のインラインメタデータという仕組みです。

Terminal window
uv init --script check.py --python 3.12
uv add --script check.py requests

ファイルの冒頭にコメント形式で依存が書き込まれます。

# /// script
# requires-python = ">=3.12"
# dependencies = [
# "requests>=2.34.2",
# ]
# ///
import requests
r = requests.get("https://httpbin.org/get", timeout=10)
print(r.status_code)

実行はそのまま uv run です。

Terminal window
uv run check.py
Installed 5 packages in 5ms
200

**仮想環境はuvが裏で用意して破棄します。**このファイルを人に渡せば、相手はuvさえ入っていれば同じように動かせます。cronに置く小さな処理や、他人に投げるワンショットの検証スクリプトで便利です。

依存を書き込みたくない場合は --with でその場限りの追加もできます。

Terminal window
uv run --with rich python -c "import rich; rich.print('[bold]hello[/bold]')"

Ruffでlintをかける

ここからRuffです。次のような雑なコードを用意します。

import os
import pandas as pd
from datetime import datetime
def load( path ):
df=pd.read_csv(path,dtype={"code":str})
unused = 1
return df
x = { 'a':1,'b':2 }

チェックします。

Terminal window
uv run ruff check .

デフォルトの出力は該当箇所と修正案まで表示する詳細形式です。件数だけ手早く見たいときは --output-format concise を使います。

Terminal window
uv run ruff check --output-format concise .
sample.py:1:1: I001 [*] Import block is un-sorted or un-formatted
sample.py:1:8: F401 [*] `os` imported but unused
sample.py:3:22: F401 [*] `datetime.datetime` imported but unused
sample.py:7:5: F841 Local variable `unused` is assigned to but never used
Found 4 errors.
[*] 3 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option).

[*] が付いているものは自動修正できます。

Terminal window
uv run ruff check --fix .
Found 4 errors (3 fixed, 1 remaining).

F841(未使用のローカル変数)が残りました。これは削除すると挙動が変わる可能性があるため、安全でない修正として保留されています。承知のうえで直すなら --unsafe-fixes を付けます。

Terminal window
uv run ruff check --fix --unsafe-fixes .

ルールの意味が分からないときは ruff rule で説明が読めます。

Terminal window
uv run ruff rule UP031
# printf-string-formatting (UP031)
Derived from the **pyupgrade** linter.
...

Ruffでフォーマットする

lintとは別コマンドです。ここが最初につまずくところで、ruff check --fix はコードスタイルを整えません。

Terminal window
uv run ruff format .
1 file reformatted, 2 files left unchanged

先ほどのコードはこうなります。

import pandas as pd
def load(path):
df = pd.read_csv(path, dtype={"code": str})
return df
x = {"a": 1, "b": 2}

black とほぼ同じ結果になります。差分を出すだけで書き換えたくないときは --diff、チェックのみなら --check です。

Terminal window
uv run ruff format --check .
1 file would be reformatted, 3 files already formatted

整形されていないファイルがあると終了コード1で落ちるので、CIやコミット前フックにそのまま使えます。

普段は ruff check --fix と ruff format の2つを続けて実行するのが基本の流れになります。

Terminal window
uv run ruff check --fix . && uv run ruff format .

pyproject.tomlで設定する

Ruffの設定は pyproject.toml に書きます。専用の設定ファイル(ruff.toml)でも構いませんが、uvと同じファイルにまとめたほうが管理は楽です。

[tool.ruff]
line-length = 100
target-version = "py312"
[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B", "SIM", "RET", "PERF"]
ignore = ["E501"]
[tool.ruff.format]
quote-style = "double"

select で指定するルール群のうち、よく使うものです。

記号 由来 内容
E/W pycodestyle PEP 8 のスタイル
F Pyflakes 未使用のimport・変数、未定義名
I isort importの並び順
UP pyupgrade 古い書き方を新しい構文へ
B flake8-bugbear バグになりやすい書き方
SIM flake8-simplify 冗長な条件式・ループの単純化
RET flake8-return return 周りの整理
PERF Perflint 遅くなりやすい書き方
ANN flake8-annotations 型注釈の欠落

デフォルトは ["E4", "E7", "E9", "F"] と控えめです。最低限 I を足しておくとimportの整列が効くので、isortを別に入れる必要がなくなります。

この設定で先ほどのコードをチェックすると、検出されるものが増えます。

sample.py:1:8: F401 [*] `os` imported but unused
sample.py:7:9: PERF401 Use a list comprehension to create a transformed list
sample.py:12:9: SIM115 Use a context manager for opening files
sample.py:19:5: RET505 [*] Unnecessary `else` after `return` statement
sample.py:24:12: UP031 Use format specifiers instead of percent format

ファイル単位で例外を作りたい場合は per-file-ignores を使います。

[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401"]
"tests/*" = ["ANN"]

1行だけ黙らせたいときは # noqa です。ルール名まで書くのが作法で、そうしないと後から理由が追えなくなります。

from .models import User # noqa: F401 # マイグレーション検出のため読み込みが必要

不要になった # noqa は RUF100 ルールで検出できます。

統計を出す

どのルールが何件出ているかを俯瞰したいときは --statistics です。既存プロジェクトにRuffを後から入れるとき、まずこれを見て select を決めます。

Terminal window
uv run ruff check --statistics .
1 PERF401 [ ] manual-list-comprehension
1 SIM115 [ ] open-file-with-context-handler
1 UP031 [ ] printf-string-formatting
1 RET505 [*] superfluous-else-return
1 F401 [*] unused-import

数千件出るようなら、いきなり全部を select せず F と I あたりから始めて、直せた分だけ追加していくのが現実的です。

VSCodeと連携する

拡張機能「Ruff」(charliermarsh.ruff)を入れて、.vscode/settings.json に次を書きます。

{
"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.ruff": "explicit",
"source.organizeImports.ruff": "explicit"
}
}
}

保存するたびにフォーマットとimport整列と自動修正がかかります。拡張機能はRuff本体をバンドルしていますが、pyproject.toml の設定は読んでくれるので、CLIとVSCodeで結果が食い違うことはありません。

既存の環境から少しずつ移す

pip の使い方をそのまま持ち込みたい場合、uvには互換インターフェースがあります。

Terminal window
uv venv
uv pip install requests
uv pip list

uv venv で .venv を作り、uv pip install でそこに入れます。pyproject.toml も uv.lock も作られないので、既存のワークフローを壊さずに「pipが速くなっただけ」の状態から始められます。

慣れてきたら uv init --bare と uv add に移していけば十分です。いきなり全部を置き換える必要はありません。

つまずきやすいところ

**ruff check --fix と ruff format は別物です。**前者はlintの自動修正、後者はコード整形です。片方だけ流して「フォーマットされない」と悩むケースが多いので、2つセットで覚えてください。

source .venv/bin/activate は基本的に不要です。uv run を通せば仮想環境のPythonが選ばれます。ただし、uv run を挟めないツール(一部のIDE設定やデバッガ)に渡すときは、.venv/bin/python を絶対パスで指定します。

**uv.lock はコミットします。**アプリケーションなら必ず入れてください。逆に、ライブラリとして配布するパッケージではコミットしないのが通例です。

**isort を別に入れる必要はありませんが、select に I がないと動きません。**デフォルトの select には含まれていないので、明示的に足してください。

**uv add は最小バージョン制約(>=)で書き込みます。**バージョンを固定したいなら uv add "pandas==3.0.5" のように明示します。とはいえ、再現性は uv.lock が担保するので、通常は >= のままで問題ありません。

**Ruffはまだ 0.x です。**マイナーバージョンの更新でルールの挙動や既定値が変わることがあります。チーム開発では uv add --dev ruff でバージョンを固定し、CIと同じものを使ってください。

おわりに

uvとRuffを入れると、Pythonの環境まわりで触るコマンドが uv add / uv run / uv sync と ruff check / ruff format の5つ程度に収束します。

特に効くのは待ち時間が消えることです。pip install pandas で数十秒待っていたところが1秒以内に終わるので、「環境を作り直すのが面倒だから使い回す」という判断がなくなります。プロジェクトごとに環境を分ける習慣が、コストなしで維持できるようになりました。

まずは uvx ruff check . を既存プロジェクトで叩いてみるところからでも十分だと思います。何も入れずに現状を確認できます。

この記事を書いた人

barorinのプロフィール画像

barorinCPA & Engineer

会計とITの二足のわらじで働く公認会計士です。Pythonを中心に、会計・監査の実務で使えるコードや、Ubuntu・Docker等の設定メモなどを書いています。